From be0ba0cabc9c3466d3884c528daff8313c59c501 Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Thu, 12 Feb 2026 13:56:48 -0800 Subject: [PATCH] feat: add TypeScript declaration files for all core libraries Add .d.ts type definitions for all four library modules: - utils.d.ts: Platform detection, file ops, hook I/O, git helpers - package-manager.d.ts: PM detection with PackageManagerName union type, DetectionSource union, and typed config interfaces - session-manager.d.ts: Session CRUD with Session, SessionMetadata, SessionStats, and SessionListResult interfaces - session-aliases.d.ts: Alias management with typed result interfaces for set, delete, rename, and cleanup operations These provide IDE autocomplete and type-checking for TypeScript consumers of the npm package without converting the source to TS. --- scripts/lib/package-manager.d.ts | 119 ++++++++++++++++++++++ scripts/lib/session-aliases.d.ts | 136 +++++++++++++++++++++++++ scripts/lib/session-manager.d.ts | 130 ++++++++++++++++++++++++ scripts/lib/utils.d.ts | 169 +++++++++++++++++++++++++++++++ 4 files changed, 554 insertions(+) create mode 100644 scripts/lib/package-manager.d.ts create mode 100644 scripts/lib/session-aliases.d.ts create mode 100644 scripts/lib/session-manager.d.ts create mode 100644 scripts/lib/utils.d.ts diff --git a/scripts/lib/package-manager.d.ts b/scripts/lib/package-manager.d.ts new file mode 100644 index 00000000..ecfa78a6 --- /dev/null +++ b/scripts/lib/package-manager.d.ts @@ -0,0 +1,119 @@ +/** + * Package Manager Detection and Selection. + * Supports: npm, pnpm, yarn, bun. + */ + +/** Supported package manager names */ +export type PackageManagerName = 'npm' | 'pnpm' | 'yarn' | 'bun'; + +/** Configuration for a single package manager */ +export interface PackageManagerConfig { + name: PackageManagerName; + /** Lock file name (e.g., "package-lock.json", "pnpm-lock.yaml") */ + lockFile: string; + /** Install command (e.g., "npm install") */ + installCmd: string; + /** Run script command prefix (e.g., "npm run", "pnpm") */ + runCmd: string; + /** Execute binary command (e.g., "npx", "pnpm dlx") */ + execCmd: string; + /** Test command (e.g., "npm test") */ + testCmd: string; + /** Build command (e.g., "npm run build") */ + buildCmd: string; + /** Dev server command (e.g., "npm run dev") */ + devCmd: string; +} + +/** How the package manager was detected */ +export type DetectionSource = + | 'environment' + | 'project-config' + | 'package.json' + | 'lock-file' + | 'global-config' + | 'default'; + +/** Result from getPackageManager() */ +export interface PackageManagerResult { + name: PackageManagerName; + config: PackageManagerConfig; + source: DetectionSource; +} + +/** Map of all supported package managers keyed by name */ +export const PACKAGE_MANAGERS: Record; + +/** Priority order for lock file detection */ +export const DETECTION_PRIORITY: PackageManagerName[]; + +export interface GetPackageManagerOptions { + /** Project directory to detect from (default: process.cwd()) */ + projectDir?: string; +} + +/** + * Get the package manager to use for the current project. + * + * Detection priority: + * 1. CLAUDE_PACKAGE_MANAGER environment variable + * 2. Project-specific config (.claude/package-manager.json) + * 3. package.json `packageManager` field + * 4. Lock file detection + * 5. Global user preference (~/.claude/package-manager.json) + * 6. Default to npm (no child processes spawned) + */ +export function getPackageManager(options?: GetPackageManagerOptions): PackageManagerResult; + +/** + * Set the user's globally preferred package manager. + * Saves to ~/.claude/package-manager.json. + * @throws If pmName is not a known package manager + */ +export function setPreferredPackageManager(pmName: PackageManagerName): { packageManager: string; setAt: string }; + +/** + * Set a project-specific preferred package manager. + * Saves to /.claude/package-manager.json. + * @throws If pmName is not a known package manager + */ +export function setProjectPackageManager(pmName: PackageManagerName, projectDir?: string): { packageManager: string; setAt: string }; + +/** + * Get package managers installed on the system. + * WARNING: Spawns child processes for each PM check. + * Do NOT call during session startup hooks. + */ +export function getAvailablePackageManagers(): PackageManagerName[]; + +/** Detect package manager from lock file in the given directory */ +export function detectFromLockFile(projectDir?: string): PackageManagerName | null; + +/** Detect package manager from package.json `packageManager` field */ +export function detectFromPackageJson(projectDir?: string): PackageManagerName | null; + +/** + * Get the full command string to run a script. + * @param script - Script name: "install", "test", "build", "dev", or custom + */ +export function getRunCommand(script: string, options?: GetPackageManagerOptions): string; + +/** + * Get the full command string to execute a package binary. + * @param binary - Binary name (e.g., "prettier", "eslint") + * @param args - Arguments to pass to the binary + */ +export function getExecCommand(binary: string, args?: string, options?: GetPackageManagerOptions): string; + +/** + * Get a message prompting the user to configure their package manager. + * Does NOT spawn child processes. + */ +export function getSelectionPrompt(): string; + +/** + * Generate a regex pattern string that matches commands for all package managers. + * @param action - Action like "dev", "install", "test", "build", or custom + * @returns Parenthesized alternation regex string, e.g., "(npm run dev|pnpm( run)? dev|...)" + */ +export function getCommandPattern(action: string): string; diff --git a/scripts/lib/session-aliases.d.ts b/scripts/lib/session-aliases.d.ts new file mode 100644 index 00000000..8c26cd38 --- /dev/null +++ b/scripts/lib/session-aliases.d.ts @@ -0,0 +1,136 @@ +/** + * Session Aliases Library for Claude Code. + * Manages named aliases for session files, stored in ~/.claude/session-aliases.json. + */ + +/** Internal alias storage entry */ +export interface AliasEntry { + sessionPath: string; + createdAt: string; + updatedAt?: string; + title: string | null; +} + +/** Alias data structure stored on disk */ +export interface AliasStore { + version: string; + aliases: Record; + metadata: { + totalCount: number; + lastUpdated: string; + }; +} + +/** Resolved alias information returned by resolveAlias */ +export interface ResolvedAlias { + alias: string; + sessionPath: string; + createdAt: string; + title: string | null; +} + +/** Alias entry returned by listAliases */ +export interface AliasListItem { + name: string; + sessionPath: string; + createdAt: string; + updatedAt?: string; + title: string | null; +} + +/** Result from mutation operations (set, delete, rename, update, cleanup) */ +export interface AliasResult { + success: boolean; + error?: string; + [key: string]: unknown; +} + +export interface SetAliasResult extends AliasResult { + isNew?: boolean; + alias?: string; + sessionPath?: string; + title?: string | null; +} + +export interface DeleteAliasResult extends AliasResult { + alias?: string; + deletedSessionPath?: string; +} + +export interface RenameAliasResult extends AliasResult { + oldAlias?: string; + newAlias?: string; + sessionPath?: string; +} + +export interface CleanupResult { + totalChecked: number; + removed: number; + removedAliases: Array<{ name: string; sessionPath: string }>; + error?: string; +} + +export interface ListAliasesOptions { + /** Filter aliases by name or title (partial match, case-insensitive) */ + search?: string | null; + /** Maximum number of aliases to return */ + limit?: number | null; +} + +/** Get the path to the aliases JSON file */ +export function getAliasesPath(): string; + +/** Load all aliases from disk. Returns default structure if file doesn't exist. */ +export function loadAliases(): AliasStore; + +/** + * Save aliases to disk with atomic write (temp file + rename). + * Creates backup before writing; restores on failure. + */ +export function saveAliases(aliases: AliasStore): boolean; + +/** + * Resolve an alias name to its session data. + * @returns Alias data, or null if not found or invalid name + */ +export function resolveAlias(alias: string): ResolvedAlias | null; + +/** + * Create or update an alias for a session. + * Alias names must be alphanumeric with dashes/underscores. + * Reserved names (list, help, remove, delete, create, set) are rejected. + */ +export function setAlias(alias: string, sessionPath: string, title?: string | null): SetAliasResult; + +/** + * List all aliases, optionally filtered and limited. + * Results are sorted by updated time (newest first). + */ +export function listAliases(options?: ListAliasesOptions): AliasListItem[]; + +/** Delete an alias by name */ +export function deleteAlias(alias: string): DeleteAliasResult; + +/** + * Rename an alias. Fails if old alias doesn't exist or new alias already exists. + * New alias name must be alphanumeric with dashes/underscores. + */ +export function renameAlias(oldAlias: string, newAlias: string): RenameAliasResult; + +/** + * Resolve an alias or pass through a session path. + * First tries to resolve as alias; if not found, returns the input as-is. + */ +export function resolveSessionAlias(aliasOrId: string): string; + +/** Update the title of an existing alias */ +export function updateAliasTitle(alias: string, title: string): AliasResult; + +/** Get all aliases that point to a specific session path */ +export function getAliasesForSession(sessionPath: string): Array<{ name: string; createdAt: string; title: string | null }>; + +/** + * Remove aliases whose sessions no longer exist. + * @param sessionExists - Function that returns true if a session path is valid + */ +export function cleanupAliases(sessionExists: (sessionPath: string) => boolean): CleanupResult; diff --git a/scripts/lib/session-manager.d.ts b/scripts/lib/session-manager.d.ts new file mode 100644 index 00000000..31e903b1 --- /dev/null +++ b/scripts/lib/session-manager.d.ts @@ -0,0 +1,130 @@ +/** + * Session Manager Library for Claude Code. + * Provides CRUD operations for session files stored as markdown in ~/.claude/sessions/. + */ + +/** Parsed metadata from a session filename */ +export interface SessionFilenameMeta { + /** Original filename */ + filename: string; + /** Short ID extracted from filename, or "no-id" for old format */ + shortId: string; + /** Date string in YYYY-MM-DD format */ + date: string; + /** Parsed Date object from the date string */ + datetime: Date; +} + +/** Metadata parsed from session markdown content */ +export interface SessionMetadata { + title: string | null; + date: string | null; + started: string | null; + lastUpdated: string | null; + completed: string[]; + inProgress: string[]; + notes: string; + context: string; +} + +/** Statistics computed from session content */ +export interface SessionStats { + totalItems: number; + completedItems: number; + inProgressItems: number; + lineCount: number; + hasNotes: boolean; + hasContext: boolean; +} + +/** A session object returned by getAllSessions and getSessionById */ +export interface Session extends SessionFilenameMeta { + /** Full filesystem path to the session file */ + sessionPath: string; + /** Whether the file has any content */ + hasContent?: boolean; + /** File size in bytes */ + size: number; + /** Last modification time */ + modifiedTime: Date; + /** File creation time (falls back to ctime on Linux) */ + createdTime: Date; + /** Session markdown content (only when includeContent=true) */ + content?: string | null; + /** Parsed metadata (only when includeContent=true) */ + metadata?: SessionMetadata; + /** Session statistics (only when includeContent=true) */ + stats?: SessionStats; +} + +/** Pagination result from getAllSessions */ +export interface SessionListResult { + sessions: Session[]; + total: number; + offset: number; + limit: number; + hasMore: boolean; +} + +export interface GetAllSessionsOptions { + /** Maximum number of sessions to return (default: 50) */ + limit?: number; + /** Number of sessions to skip (default: 0) */ + offset?: number; + /** Filter by date in YYYY-MM-DD format */ + date?: string | null; + /** Search in short ID */ + search?: string | null; +} + +/** + * Parse a session filename to extract date and short ID. + * @returns Parsed metadata, or null if the filename doesn't match the expected pattern + */ +export function parseSessionFilename(filename: string): SessionFilenameMeta | null; + +/** Get the full filesystem path for a session filename */ +export function getSessionPath(filename: string): string; + +/** + * Read session markdown content from disk. + * @returns Content string, or null if the file doesn't exist + */ +export function getSessionContent(sessionPath: string): string | null; + +/** Parse session metadata from markdown content */ +export function parseSessionMetadata(content: string | null): SessionMetadata; + +/** + * Calculate statistics for a session. + * Accepts either a file path (ending in .tmp) or pre-read content string. + */ +export function getSessionStats(sessionPathOrContent: string): SessionStats; + +/** Get the title from a session file, or "Untitled Session" if none */ +export function getSessionTitle(sessionPath: string): string; + +/** Get human-readable file size (e.g., "1.2 KB") */ +export function getSessionSize(sessionPath: string): string; + +/** Get all sessions with optional filtering and pagination */ +export function getAllSessions(options?: GetAllSessionsOptions): SessionListResult; + +/** + * Find a session by short ID or filename. + * @param sessionId - Short ID prefix, full filename, or filename without .tmp + * @param includeContent - Whether to read and parse the session content + */ +export function getSessionById(sessionId: string, includeContent?: boolean): Session | null; + +/** Write markdown content to a session file */ +export function writeSessionContent(sessionPath: string, content: string): boolean; + +/** Append content to an existing session file */ +export function appendSessionContent(sessionPath: string, content: string): boolean; + +/** Delete a session file */ +export function deleteSession(sessionPath: string): boolean; + +/** Check if a session file exists and is a regular file */ +export function sessionExists(sessionPath: string): boolean; diff --git a/scripts/lib/utils.d.ts b/scripts/lib/utils.d.ts new file mode 100644 index 00000000..7c5a0b18 --- /dev/null +++ b/scripts/lib/utils.d.ts @@ -0,0 +1,169 @@ +/** + * Cross-platform utility functions for Claude Code hooks and scripts. + * Works on Windows, macOS, and Linux. + */ + +import type { ExecSyncOptions } from 'child_process'; + +// Platform detection +export const isWindows: boolean; +export const isMacOS: boolean; +export const isLinux: boolean; + +// --- Directories --- + +/** Get the user's home directory (cross-platform) */ +export function getHomeDir(): string; + +/** Get the Claude config directory (~/.claude) */ +export function getClaudeDir(): string; + +/** Get the sessions directory (~/.claude/sessions) */ +export function getSessionsDir(): string; + +/** Get the learned skills directory (~/.claude/skills/learned) */ +export function getLearnedSkillsDir(): string; + +/** Get the temp directory (cross-platform) */ +export function getTempDir(): string; + +/** + * Ensure a directory exists, creating it recursively if needed. + * Handles EEXIST race conditions from concurrent creation. + * @throws If directory cannot be created (e.g., permission denied) + */ +export function ensureDir(dirPath: string): string; + +// --- Date/Time --- + +/** Get current date in YYYY-MM-DD format */ +export function getDateString(): string; + +/** Get current time in HH:MM format */ +export function getTimeString(): string; + +/** Get current datetime in YYYY-MM-DD HH:MM:SS format */ +export function getDateTimeString(): string; + +// --- Session/Project --- + +/** + * Get short session ID from CLAUDE_SESSION_ID environment variable. + * Returns last 8 characters, falls back to project name then the provided fallback. + */ +export function getSessionIdShort(fallback?: string): string; + +/** Get the git repository name from the current working directory */ +export function getGitRepoName(): string | null; + +/** Get project name from git repo or current directory basename */ +export function getProjectName(): string | null; + +// --- File operations --- + +export interface FileMatch { + /** Absolute path to the matching file */ + path: string; + /** Modification time in milliseconds since epoch */ + mtime: number; +} + +export interface FindFilesOptions { + /** Maximum age in days. Only files modified within this many days are returned. */ + maxAge?: number | null; + /** Whether to search subdirectories recursively */ + recursive?: boolean; +} + +/** + * Find files matching a glob-like pattern in a directory. + * Supports `*` (any chars), `?` (single char), and `.` (literal dot). + * Results are sorted by modification time (newest first). + */ +export function findFiles(dir: string, pattern: string, options?: FindFilesOptions): FileMatch[]; + +/** + * Read a text file safely. Returns null if the file doesn't exist or can't be read. + */ +export function readFile(filePath: string): string | null; + +/** Write a text file, creating parent directories if needed */ +export function writeFile(filePath: string, content: string): void; + +/** Append to a text file, creating parent directories if needed */ +export function appendFile(filePath: string, content: string): void; + +/** + * Replace text in a file (cross-platform sed alternative). + * @returns true if the file was found and updated, false if file not found + */ +export function replaceInFile(filePath: string, search: string | RegExp, replace: string): boolean; + +/** + * Count occurrences of a pattern in a file. + * The global flag is enforced automatically for correct counting. + */ +export function countInFile(filePath: string, pattern: string | RegExp): number; + +export interface GrepMatch { + /** 1-based line number */ + lineNumber: number; + /** Full content of the matching line */ + content: string; +} + +/** Search for a pattern in a file and return matching lines with line numbers */ +export function grepFile(filePath: string, pattern: string | RegExp): GrepMatch[]; + +// --- Hook I/O --- + +export interface ReadStdinJsonOptions { + /** + * Timeout in milliseconds. Prevents hooks from hanging indefinitely + * if stdin never closes. Default: 5000 + */ + timeoutMs?: number; +} + +/** + * Read JSON from stdin (for hook input). + * Returns an empty object if stdin is empty or times out. + */ +export function readStdinJson(options?: ReadStdinJsonOptions): Promise>; + +/** Log a message to stderr (visible to user in Claude Code terminal) */ +export function log(message: string): void; + +/** Output data to stdout (returned to Claude's context) */ +export function output(data: string | Record): void; + +// --- System --- + +/** + * Check if a command exists in PATH. + * Only allows alphanumeric, dash, underscore, and dot characters. + * WARNING: Spawns a child process (where.exe on Windows, which on Unix). + */ +export function commandExists(cmd: string): boolean; + +export interface CommandResult { + success: boolean; + /** Trimmed stdout on success, stderr or error message on failure */ + output: string; +} + +/** + * Run a shell command and return the output. + * SECURITY: Only use with trusted, hardcoded commands. + * Never pass user-controlled input directly. + */ +export function runCommand(cmd: string, options?: ExecSyncOptions): CommandResult; + +/** Check if the current directory is inside a git repository */ +export function isGitRepo(): boolean; + +/** + * Get git modified files (staged + unstaged), optionally filtered by regex patterns. + * Invalid regex patterns are silently skipped. + */ +export function getGitModifiedFiles(patterns?: string[]): string[];