@eldrforge/ai-service 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js.map +1 -0
  3. package/dist/src/ai.d.ts +55 -0
  4. package/{src/index.ts → dist/src/index.d.ts} +1 -2
  5. package/dist/src/interactive.d.ts +122 -0
  6. package/dist/src/logger.d.ts +19 -0
  7. package/dist/src/prompts/commit.d.ts +29 -0
  8. package/dist/src/prompts/index.d.ts +10 -0
  9. package/dist/src/prompts/release.d.ts +25 -0
  10. package/dist/src/prompts/review.d.ts +21 -0
  11. package/dist/src/types.d.ts +99 -0
  12. package/package.json +11 -8
  13. package/.github/dependabot.yml +0 -12
  14. package/.github/workflows/npm-publish.yml +0 -48
  15. package/.github/workflows/test.yml +0 -33
  16. package/eslint.config.mjs +0 -84
  17. package/src/ai.ts +0 -421
  18. package/src/interactive.ts +0 -562
  19. package/src/logger.ts +0 -69
  20. package/src/prompts/commit.ts +0 -85
  21. package/src/prompts/index.ts +0 -28
  22. package/src/prompts/instructions/commit.md +0 -133
  23. package/src/prompts/instructions/release.md +0 -188
  24. package/src/prompts/instructions/review.md +0 -169
  25. package/src/prompts/personas/releaser.md +0 -24
  26. package/src/prompts/personas/you.md +0 -55
  27. package/src/prompts/release.ts +0 -118
  28. package/src/prompts/review.ts +0 -72
  29. package/src/types.ts +0 -112
  30. package/tests/ai-complete-coverage.test.ts +0 -241
  31. package/tests/ai-create-completion.test.ts +0 -288
  32. package/tests/ai-edge-cases.test.ts +0 -221
  33. package/tests/ai-openai-error.test.ts +0 -35
  34. package/tests/ai-transcribe.test.ts +0 -169
  35. package/tests/ai.test.ts +0 -139
  36. package/tests/interactive-editor.test.ts +0 -253
  37. package/tests/interactive-secure-temp.test.ts +0 -264
  38. package/tests/interactive-user-choice.test.ts +0 -173
  39. package/tests/interactive-user-text.test.ts +0 -174
  40. package/tests/interactive.test.ts +0 -94
  41. package/tests/logger-noop.test.ts +0 -40
  42. package/tests/logger.test.ts +0 -122
  43. package/tests/prompts.test.ts +0 -179
  44. package/tsconfig.json +0 -35
  45. package/vite.config.ts +0 -69
  46. package/vitest.config.ts +0 -25
@@ -0,0 +1,2 @@
1
+ export * from './src/index'
2
+ export {}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/logger.ts","../src/ai.ts","../src/interactive.ts","../src/prompts/commit.ts","../src/prompts/release.ts","../src/prompts/review.ts"],"sourcesContent":["/**\n * Optional logger support\n * If winston is available as a peer dependency, use it\n * Otherwise, provide a no-op logger\n */\n\nimport type { Logger } from './types';\n\nlet logger: Logger | undefined;\n\n/**\n * Set a custom logger instance\n */\nexport function setLogger(customLogger: Logger): void {\n logger = customLogger;\n}\n\n/**\n * Create a no-op logger that does nothing\n */\nexport function createNoOpLogger(): Logger {\n return {\n info: () => {},\n error: () => {},\n warn: () => {},\n debug: () => {},\n };\n}\n\n/**\n * Attempt to load winston logger\n * @returns winston logger if available, otherwise null\n */\nexport function tryLoadWinston(): Logger | null {\n try {\n // Dynamic import to avoid hard dependency\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const winston = require('winston');\n if (winston && winston.createLogger) {\n return winston.createLogger({\n level: 'info',\n format: winston.format.simple(),\n transports: [new winston.transports.Console()],\n });\n }\n } catch {\n // Winston not available\n }\n return null;\n}\n\n/**\n * Get the current logger or a no-op logger\n */\nexport function getLogger(): Logger {\n if (logger) {\n return logger;\n }\n\n // Try to load winston if available\n const winstonLogger = tryLoadWinston();\n if (winstonLogger) {\n logger = winstonLogger;\n return winstonLogger;\n }\n\n // Return no-op logger\n return createNoOpLogger();\n}\n","import { OpenAI } from 'openai';\nimport type { ChatCompletionMessageParam } from 'openai/resources';\nimport { safeJsonParse } from '@eldrforge/git-tools';\nimport fs from 'fs';\nimport { getLogger } from './logger';\nimport type { AIConfig, Transcription, StorageAdapter, Logger } from './types';\n\nexport interface OpenAIOptions {\n responseFormat?: any;\n model?: string;\n debug?: boolean;\n debugFile?: string;\n debugRequestFile?: string;\n debugResponseFile?: string;\n maxTokens?: number;\n openaiReasoning?: 'low' | 'medium' | 'high';\n openaiMaxOutputTokens?: number;\n storage?: StorageAdapter;\n logger?: Logger;\n}\n\nexport interface TranscriptionOptions {\n model?: string;\n debug?: boolean;\n debugFile?: string;\n debugRequestFile?: string;\n debugResponseFile?: string;\n outputDirectory?: string;\n storage?: StorageAdapter;\n logger?: Logger;\n onArchive?: (audioPath: string, transcriptionText: string) => Promise<void>;\n}\n\n/**\n * Get the appropriate model to use based on command-specific configuration\n * Command-specific model overrides the global model setting\n */\nexport function getModelForCommand(config: AIConfig, commandName: string): string {\n let commandModel: string | undefined;\n\n switch (commandName) {\n case 'commit':\n case 'audio-commit':\n commandModel = config.commands?.commit?.model;\n break;\n case 'release':\n commandModel = config.commands?.release?.model;\n break;\n case 'review':\n case 'audio-review':\n commandModel = config.commands?.review?.model;\n break;\n default:\n // For other commands, just use global model\n break;\n }\n\n // Return command-specific model if available, otherwise global model\n return commandModel || config.model || 'gpt-4o-mini';\n}\n\n/**\n * Get the appropriate OpenAI reasoning level based on command-specific configuration\n * Command-specific reasoning overrides the global reasoning setting\n */\nexport function getOpenAIReasoningForCommand(config: AIConfig, commandName: string): 'low' | 'medium' | 'high' {\n let commandReasoning: 'low' | 'medium' | 'high' | undefined;\n\n switch (commandName) {\n case 'commit':\n case 'audio-commit':\n commandReasoning = config.commands?.commit?.reasoning;\n break;\n case 'release':\n commandReasoning = config.commands?.release?.reasoning;\n break;\n case 'review':\n case 'audio-review':\n commandReasoning = config.commands?.review?.reasoning;\n break;\n default:\n // For other commands, just use global reasoning\n break;\n }\n\n // Return command-specific reasoning if available, otherwise global reasoning\n return commandReasoning || config.reasoning || 'low';\n}\n\nexport class OpenAIError extends Error {\n constructor(message: string, public readonly isTokenLimitError: boolean = false) {\n super(message);\n this.name = 'OpenAIError';\n }\n}\n\n// Check if an error is a token limit exceeded error\nexport function isTokenLimitError(error: any): boolean {\n if (!error?.message) return false;\n\n const message = error.message.toLowerCase();\n return message.includes('maximum context length') ||\n message.includes('context_length_exceeded') ||\n message.includes('token limit') ||\n message.includes('too many tokens') ||\n message.includes('reduce the length');\n}\n\n// Check if an error is a rate limit error\nexport function isRateLimitError(error: any): boolean {\n if (!error?.message && !error?.code && !error?.status) return false;\n\n // Check for OpenAI specific rate limit indicators\n if (error.status === 429 || error.code === 'rate_limit_exceeded') {\n return true;\n }\n\n // Only check message if it exists\n if (error.message) {\n const message = error.message.toLowerCase();\n return message.includes('rate limit exceeded') ||\n message.includes('too many requests') ||\n message.includes('quota exceeded') ||\n (message.includes('rate') && message.includes('limit'));\n }\n\n return false;\n}\n\n/**\n * Create OpenAI completion with optional debug and retry support\n */\nexport async function createCompletion(\n messages: ChatCompletionMessageParam[],\n options: OpenAIOptions = { model: \"gpt-4o-mini\" }\n): Promise<string | any> {\n const logger = options.logger || getLogger();\n let openai: OpenAI | null = null;\n\n try {\n const apiKey = process.env.OPENAI_API_KEY;\n if (!apiKey) {\n throw new OpenAIError('OPENAI_API_KEY environment variable is not set');\n }\n\n // Create the client which we'll close in the finally block.\n const timeoutMs = parseInt(process.env.OPENAI_TIMEOUT_MS || '300000'); // Default to 5 minutes\n openai = new OpenAI({\n apiKey: apiKey,\n timeout: timeoutMs,\n });\n\n const modelToUse = options.model || \"gpt-4o-mini\";\n\n // Calculate request size\n const requestSize = JSON.stringify(messages).length;\n const requestSizeKB = (requestSize / 1024).toFixed(2);\n\n // Log model, reasoning level, and request size\n const reasoningInfo = options.openaiReasoning ? ` | Reasoning: ${options.openaiReasoning}` : '';\n logger.info('🤖 Making request to OpenAI');\n logger.info(' Model: %s%s', modelToUse, reasoningInfo);\n logger.info(' Request size: %s KB (%s bytes)', requestSizeKB, requestSize.toLocaleString());\n\n logger.debug('Sending prompt to OpenAI: %j', messages);\n\n // Use openaiMaxOutputTokens if specified (highest priority), otherwise fall back to maxTokens, or default to 10000\n const maxCompletionTokens = options.openaiMaxOutputTokens ?? options.maxTokens ?? 10000;\n\n // Save request debug file if enabled\n if (options.debug && (options.debugRequestFile || options.debugFile) && options.storage) {\n const requestData = {\n model: modelToUse,\n messages,\n max_completion_tokens: maxCompletionTokens,\n response_format: options.responseFormat,\n reasoning_effort: options.openaiReasoning,\n };\n const debugFile = options.debugRequestFile || options.debugFile;\n await options.storage.writeTemp(debugFile!, JSON.stringify(requestData, null, 2));\n logger.debug('Wrote request debug file to %s', debugFile);\n }\n\n // Prepare the API call options\n const apiOptions: any = {\n model: modelToUse,\n messages,\n max_completion_tokens: maxCompletionTokens,\n response_format: options.responseFormat,\n };\n\n // Add reasoning parameter if specified and model supports it\n if (options.openaiReasoning && (modelToUse.includes('gpt-5') || modelToUse.includes('o3'))) {\n apiOptions.reasoning_effort = options.openaiReasoning;\n }\n\n // Add timeout wrapper to the OpenAI API call\n const startTime = Date.now();\n const completionPromise = openai.chat.completions.create(apiOptions);\n\n // Create timeout promise with proper cleanup to prevent memory leaks\n let timeoutId: NodeJS.Timeout | null = null;\n const timeoutPromise = new Promise<never>((_, reject) => {\n const timeoutMs = parseInt(process.env.OPENAI_TIMEOUT_MS || '300000'); // Default to 5 minutes\n timeoutId = setTimeout(() => reject(new OpenAIError(`OpenAI API call timed out after ${timeoutMs/1000} seconds`)), timeoutMs);\n });\n\n let completion;\n try {\n completion = await Promise.race([completionPromise, timeoutPromise]);\n } finally {\n // Clear the timeout to prevent memory leaks\n if (timeoutId !== null) {\n clearTimeout(timeoutId);\n }\n }\n\n const elapsedTime = Date.now() - startTime;\n\n // Save response debug file if enabled\n if (options.debug && (options.debugResponseFile || options.debugFile) && options.storage) {\n const debugFile = options.debugResponseFile || options.debugFile;\n await options.storage.writeTemp(debugFile!, JSON.stringify(completion, null, 2));\n logger.debug('Wrote response debug file to %s', debugFile);\n }\n\n const response = completion.choices[0]?.message?.content?.trim();\n if (!response) {\n throw new OpenAIError('No response received from OpenAI');\n }\n\n // Calculate and log response size\n const responseSize = response.length;\n const responseSizeKB = (responseSize / 1024).toFixed(2);\n logger.info(' Response size: %s KB (%s bytes)', responseSizeKB, responseSize.toLocaleString());\n\n // Log elapsed time\n const elapsedTimeFormatted = elapsedTime >= 1000\n ? `${(elapsedTime / 1000).toFixed(1)}s`\n : `${elapsedTime}ms`;\n logger.info(' Time: %s', elapsedTimeFormatted);\n\n // Log token usage if available\n if (completion.usage) {\n logger.info(' Token usage: %s prompt + %s completion = %s total',\n completion.usage.prompt_tokens?.toLocaleString() || '?',\n completion.usage.completion_tokens?.toLocaleString() || '?',\n completion.usage.total_tokens?.toLocaleString() || '?'\n );\n }\n\n logger.debug('Received response from OpenAI: %s...', response.substring(0, 30));\n if (options.responseFormat) {\n return safeJsonParse(response, 'OpenAI API response');\n } else {\n return response;\n }\n\n } catch (error: any) {\n logger.error('Error calling OpenAI API: %s %s', error.message, error.stack);\n const isTokenError = isTokenLimitError(error);\n throw new OpenAIError(`Failed to create completion: ${error.message}`, isTokenError);\n } finally {\n // OpenAI client cleanup is handled automatically by the library\n // No manual cleanup needed for newer versions\n }\n}\n\n/**\n * Create completion with automatic retry on token limit errors\n */\nexport async function createCompletionWithRetry(\n messages: ChatCompletionMessageParam[],\n options: OpenAIOptions = { model: \"gpt-4o-mini\" },\n retryCallback?: (attempt: number) => Promise<ChatCompletionMessageParam[]>\n): Promise<string | any> {\n const logger = options.logger || getLogger();\n const maxRetries = 3;\n\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n try {\n const messagesToSend = attempt === 1 ? messages : (retryCallback ? await retryCallback(attempt) : messages);\n return await createCompletion(messagesToSend, options);\n } catch (error: any) {\n if (error instanceof OpenAIError && error.isTokenLimitError && attempt < maxRetries && retryCallback) {\n logger.warn('Token limit exceeded on attempt %d/%d, retrying with reduced content...', attempt, maxRetries);\n // Add exponential backoff for token limit errors\n const backoffMs = Math.min(1000 * Math.pow(2, attempt - 1), 10000);\n await new Promise(resolve => setTimeout(resolve, backoffMs));\n continue;\n } else if (isRateLimitError(error) && attempt < maxRetries) {\n // Handle rate limiting with exponential backoff\n const backoffMs = Math.min(2000 * Math.pow(2, attempt - 1), 15000); // More reasonable backoff: 2s, 4s, 8s, max 15s\n logger.warn(`Rate limit hit on attempt ${attempt}/${maxRetries}, waiting ${backoffMs}ms before retry...`);\n await new Promise(resolve => setTimeout(resolve, backoffMs));\n continue;\n }\n throw error;\n }\n }\n\n // This should never be reached, but TypeScript requires it\n throw new OpenAIError('Max retries exceeded');\n}\n\n/**\n * Transcribe audio file using OpenAI Whisper API\n */\nexport async function transcribeAudio(\n filePath: string,\n options: TranscriptionOptions = { model: \"whisper-1\" }\n): Promise<Transcription> {\n const logger = options.logger || getLogger();\n let openai: OpenAI | null = null;\n let audioStream: fs.ReadStream | null = null;\n let streamClosed = false;\n\n // Helper function to safely close the stream\n const closeAudioStream = () => {\n if (audioStream && !streamClosed) {\n try {\n // Only call destroy if it exists and the stream isn't already destroyed\n if (typeof audioStream.destroy === 'function' && !audioStream.destroyed) {\n audioStream.destroy();\n }\n streamClosed = true;\n logger.debug('Audio stream closed successfully');\n } catch (streamErr) {\n logger.debug('Failed to destroy audio read stream: %s', (streamErr as Error).message);\n streamClosed = true; // Mark as closed even if destroy failed\n }\n }\n };\n\n try {\n const apiKey = process.env.OPENAI_API_KEY;\n if (!apiKey) {\n throw new OpenAIError('OPENAI_API_KEY environment variable is not set');\n }\n\n openai = new OpenAI({\n apiKey: apiKey,\n });\n\n logger.debug('Transcribing audio file: %s', filePath);\n\n // Save request debug file if enabled\n if (options.debug && (options.debugRequestFile || options.debugFile) && options.storage) {\n const requestData = {\n model: options.model || \"whisper-1\",\n file: filePath, // Can't serialize the stream, so just save the file path\n response_format: \"json\",\n };\n const debugFile = options.debugRequestFile || options.debugFile;\n await options.storage.writeTemp(debugFile!, JSON.stringify(requestData, null, 2));\n logger.debug('Wrote request debug file to %s', debugFile);\n }\n\n audioStream = fs.createReadStream(filePath);\n\n // Set up error handler for the stream to ensure cleanup on stream errors\n // Only add handler if the stream has the 'on' method (real streams)\n if (audioStream && typeof audioStream.on === 'function') {\n audioStream.on('error', (streamError) => {\n logger.error('Audio stream error: %s', streamError.message);\n closeAudioStream();\n });\n }\n\n let transcription;\n try {\n transcription = await openai.audio.transcriptions.create({\n model: options.model || \"whisper-1\",\n file: audioStream,\n response_format: \"json\",\n });\n // Close the stream immediately after successful API call to prevent race conditions\n closeAudioStream();\n } catch (apiError) {\n // Close the stream immediately if the API call fails\n closeAudioStream();\n throw apiError;\n }\n\n // Save response debug file if enabled\n if (options.debug && (options.debugResponseFile || options.debugFile) && options.storage) {\n const debugFile = options.debugResponseFile || options.debugFile;\n await options.storage.writeTemp(debugFile!, JSON.stringify(transcription, null, 2));\n logger.debug('Wrote response debug file to %s', debugFile);\n }\n\n const response = transcription;\n if (!response) {\n throw new OpenAIError('No transcription received from OpenAI');\n }\n\n logger.debug('Received transcription from OpenAI: %s', response);\n\n // Archive the audio file and transcription if callback provided\n if (options.onArchive) {\n try {\n await options.onArchive(filePath, response.text);\n } catch (archiveError: any) {\n // Don't fail the transcription if archiving fails, just log the error\n logger.warn('Failed to archive audio file: %s', archiveError.message);\n }\n }\n\n return response;\n\n } catch (error: any) {\n logger.error('Error transcribing audio file: %s %s', error.message, error.stack);\n throw new OpenAIError(`Failed to transcribe audio: ${error.message}`);\n } finally {\n // Ensure the audio stream is properly closed to release file handles\n closeAudioStream();\n // OpenAI client cleanup is handled automatically by the library\n // No manual cleanup needed for newer versions\n }\n}\n\n","#!/usr/bin/env node\nimport { getLogger } from './logger';\nimport type { Logger, Choice, InteractiveOptions } from './types';\nimport { spawnSync } from 'child_process';\nimport * as path from 'path';\nimport * as os from 'os';\nimport * as fs from 'fs/promises';\n\n/**\n * Get user choice interactively from terminal input\n * @param prompt The prompt message to display\n * @param choices Array of available choices\n * @param options Additional options for customizing behavior\n * @returns Promise resolving to the selected choice key\n */\nexport async function getUserChoice(\n prompt: string,\n choices: Choice[],\n options: InteractiveOptions = {}\n): Promise<string> {\n const logger = options.logger || getLogger();\n\n logger.info(prompt);\n choices.forEach(choice => {\n logger.info(` [${choice.key}] ${choice.label}`);\n });\n logger.info('');\n\n // Check if stdin is a TTY (terminal) or piped\n if (!process.stdin.isTTY) {\n logger.error('⚠️ STDIN is piped but interactive mode is enabled');\n logger.error(' Interactive prompts cannot be used when input is piped');\n logger.error(' Solutions:');\n logger.error(' • Use terminal input instead of piping');\n\n // Add any additional suggestions\n if (options.nonTtyErrorSuggestions) {\n options.nonTtyErrorSuggestions.forEach(suggestion => {\n logger.error(` • ${suggestion}`);\n });\n }\n\n return 's'; // Default to skip\n }\n\n return new Promise((resolve, reject) => {\n let isResolved = false;\n let dataHandler: ((key: Buffer) => void) | null = null;\n let errorHandler: ((error: Error) => void) | null = null;\n\n const cleanup = () => {\n if (dataHandler) {\n process.stdin.removeListener('data', dataHandler);\n }\n if (errorHandler) {\n process.stdin.removeListener('error', errorHandler);\n }\n\n try {\n if (process.stdin.setRawMode) {\n process.stdin.setRawMode(false);\n }\n process.stdin.pause();\n // Detach stdin again now that we're done\n if (typeof process.stdin.unref === 'function') {\n process.stdin.unref();\n }\n } catch {\n // Ignore cleanup errors\n }\n };\n\n const safeResolve = (value: string) => {\n if (!isResolved) {\n isResolved = true;\n cleanup();\n resolve(value);\n }\n };\n\n const safeReject = (error: Error) => {\n if (!isResolved) {\n isResolved = true;\n cleanup();\n reject(error);\n }\n };\n\n try {\n // Ensure stdin is referenced so the process doesn't exit while waiting for input\n if (typeof process.stdin.ref === 'function') {\n process.stdin.ref();\n }\n\n process.stdin.setRawMode(true);\n process.stdin.resume();\n\n dataHandler = (key: Buffer) => {\n try {\n const keyStr = key.toString().toLowerCase();\n const choice = choices.find(c => c.key === keyStr);\n if (choice) {\n logger.info(`Selected: ${choice.label}\\n`);\n safeResolve(choice.key);\n }\n } catch (error) {\n safeReject(error instanceof Error ? error : new Error('Unknown error processing input'));\n }\n };\n\n errorHandler = (error: Error) => {\n safeReject(error);\n };\n\n process.stdin.on('data', dataHandler);\n process.stdin.on('error', errorHandler);\n\n } catch (error) {\n safeReject(error instanceof Error ? error : new Error('Failed to setup input handlers'));\n }\n });\n}\n\n/**\n * Secure temporary file handle that prevents TOCTOU vulnerabilities\n */\nexport class SecureTempFile {\n private fd: fs.FileHandle | null = null;\n private filePath: string;\n private isCleanedUp = false;\n private logger: Logger;\n\n private constructor(filePath: string, fd: fs.FileHandle, logger?: Logger) {\n this.filePath = filePath;\n this.fd = fd;\n this.logger = logger || getLogger();\n }\n\n /**\n * Create a secure temporary file with proper permissions and atomic operations\n * @param prefix Prefix for the temporary filename\n * @param extension File extension (e.g., '.txt', '.md')\n * @param logger Optional logger instance\n * @returns Promise resolving to SecureTempFile instance\n */\n static async create(prefix: string = 'ai-service', extension: string = '.txt', logger?: Logger): Promise<SecureTempFile> {\n const tmpDir = os.tmpdir();\n const log = logger || getLogger();\n\n // Ensure temp directory exists and is writable (skip check in test environments)\n if (!process.env.VITEST) {\n try {\n await fs.access(tmpDir, fs.constants.W_OK);\n } catch (error: any) {\n // Try to create the directory if it doesn't exist\n try {\n await fs.mkdir(tmpDir, { recursive: true, mode: 0o700 });\n } catch (mkdirError: any) {\n throw new Error(`Temp directory not writable: ${tmpDir} - ${error.message}. Failed to create: ${mkdirError.message}`);\n }\n }\n }\n\n const tmpFilePath = path.join(tmpDir, `${prefix}_${Date.now()}_${Math.random().toString(36).substring(7)}${extension}`);\n\n // Create file with exclusive access and restrictive permissions (owner read/write only)\n // Using 'wx' flag ensures exclusive creation (fails if file exists)\n let fd: fs.FileHandle;\n try {\n fd = await fs.open(tmpFilePath, 'wx', 0o600);\n } catch (error: any) {\n if (error.code === 'EEXIST') {\n // Highly unlikely with timestamp + random suffix, but handle it\n throw new Error(`Temporary file already exists: ${tmpFilePath}`);\n }\n throw new Error(`Failed to create temporary file: ${error.message}`);\n }\n\n return new SecureTempFile(tmpFilePath, fd, log);\n }\n\n /**\n * Get the file path (use with caution in external commands)\n */\n get path(): string {\n if (this.isCleanedUp) {\n throw new Error('Temp file has been cleaned up');\n }\n return this.filePath;\n }\n\n /**\n * Write content to the temporary file\n */\n async writeContent(content: string): Promise<void> {\n if (!this.fd || this.isCleanedUp) {\n throw new Error('Temp file is not available for writing');\n }\n await this.fd.writeFile(content, 'utf8');\n }\n\n /**\n * Read content from the temporary file\n */\n async readContent(): Promise<string> {\n if (!this.fd || this.isCleanedUp) {\n throw new Error('Temp file is not available for reading');\n }\n const content = await this.fd.readFile('utf8');\n return content;\n }\n\n /**\n * Close the file handle\n */\n async close(): Promise<void> {\n if (this.fd && !this.isCleanedUp) {\n await this.fd.close();\n this.fd = null;\n }\n }\n\n /**\n * Securely cleanup the temporary file - prevents TOCTOU by using file descriptor\n */\n async cleanup(): Promise<void> {\n if (this.isCleanedUp) {\n return; // Already cleaned up\n }\n\n try {\n // Close file descriptor first if still open\n if (this.fd) {\n await this.fd.close();\n this.fd = null;\n }\n\n // Now safely remove the file\n // Use fs.unlink which is safer than checking existence first\n await fs.unlink(this.filePath);\n } catch (error: any) {\n // Only ignore ENOENT (file not found) errors\n if (error.code !== 'ENOENT') {\n this.logger.warn(`Failed to cleanup temp file ${this.filePath}: ${error.message}`);\n // Don't throw here to avoid masking main operations\n }\n } finally {\n this.isCleanedUp = true;\n }\n }\n}\n\n/**\n * Create a secure temporary file for editing with proper permissions\n * @param prefix Prefix for the temporary filename\n * @param extension File extension (e.g., '.txt', '.md')\n * @param logger Optional logger instance\n * @returns Promise resolving to the temporary file path\n * @deprecated Use SecureTempFile.create() for better security\n */\nexport async function createSecureTempFile(prefix: string = 'ai-service', extension: string = '.txt', logger?: Logger): Promise<string> {\n const secureTempFile = await SecureTempFile.create(prefix, extension, logger);\n await secureTempFile.close();\n return secureTempFile.path;\n}\n\n/**\n * Clean up a temporary file\n * @param filePath Path to the temporary file to clean up\n * @param logger Optional logger instance\n * @deprecated Use SecureTempFile.cleanup() for better security\n */\nexport async function cleanupTempFile(filePath: string, logger?: Logger): Promise<void> {\n const log = logger || getLogger();\n try {\n await fs.unlink(filePath);\n } catch (error: any) {\n // Only ignore ENOENT (file not found) errors\n if (error.code !== 'ENOENT') {\n log.warn(`Failed to cleanup temp file ${filePath}: ${error.message}`);\n }\n }\n}\n\nexport interface EditorResult {\n content: string;\n wasEdited: boolean;\n}\n\n/**\n * Open content in user's editor for editing\n * @param content Initial content to edit\n * @param templateLines Additional template lines to include (will be filtered out)\n * @param fileExtension File extension for syntax highlighting\n * @param editor Editor command to use (defaults to EDITOR/VISUAL env var or 'vi')\n * @param logger Optional logger instance\n * @returns Promise resolving to the edited content\n */\nexport async function editContentInEditor(\n content: string,\n templateLines: string[] = [],\n fileExtension: string = '.txt',\n editor?: string,\n logger?: Logger\n): Promise<EditorResult> {\n const log = logger || getLogger();\n const editorCmd = editor || process.env.EDITOR || process.env.VISUAL || 'vi';\n\n const secureTempFile = await SecureTempFile.create('ai-service_edit', fileExtension, log);\n try {\n // Build template content\n const templateContent = [\n ...templateLines,\n ...(templateLines.length > 0 ? [''] : []), // Add separator if we have template lines\n content,\n '',\n ].join('\\n');\n\n await secureTempFile.writeContent(templateContent);\n await secureTempFile.close(); // Close before external editor access\n\n log.info(`📝 Opening ${editorCmd} to edit content...`);\n\n // Open the editor synchronously\n const result = spawnSync(editorCmd, [secureTempFile.path], { stdio: 'inherit' });\n\n if (result.error) {\n throw new Error(`Failed to launch editor '${editorCmd}': ${result.error.message}`);\n }\n\n // Read the file back in, stripping comment lines\n const fileContent = (await fs.readFile(secureTempFile.path, 'utf8'))\n .split('\\n')\n .filter(line => !line.trim().startsWith('#'))\n .join('\\n')\n .trim();\n\n if (!fileContent) {\n throw new Error('Content is empty after editing');\n }\n\n log.info('✅ Content updated successfully');\n\n return {\n content: fileContent,\n wasEdited: fileContent !== content.trim()\n };\n\n } finally {\n // Always clean up the temp file securely\n await secureTempFile.cleanup();\n }\n}\n\n/**\n * Standard choices for interactive feedback loops\n */\nexport const STANDARD_CHOICES = {\n CONFIRM: { key: 'c', label: 'Confirm and proceed' },\n EDIT: { key: 'e', label: 'Edit in editor' },\n SKIP: { key: 's', label: 'Skip and abort' },\n IMPROVE: { key: 'i', label: 'Improve with LLM feedback' }\n} as const;\n\n/**\n * Get text input from the user\n * @param prompt The prompt message to display\n * @param options Additional options for customizing behavior\n * @returns Promise resolving to the user's text input\n */\nexport async function getUserTextInput(\n prompt: string,\n options: InteractiveOptions = {}\n): Promise<string> {\n const logger = options.logger || getLogger();\n\n // Check if stdin is a TTY (terminal) or piped\n if (!process.stdin.isTTY) {\n logger.error('⚠️ STDIN is piped but interactive text input is required');\n logger.error(' Interactive text input cannot be used when input is piped');\n logger.error(' Solutions:');\n logger.error(' • Use terminal input instead of piping');\n\n // Add any additional suggestions\n if (options.nonTtyErrorSuggestions) {\n options.nonTtyErrorSuggestions.forEach(suggestion => {\n logger.error(` • ${suggestion}`);\n });\n }\n\n throw new Error('Interactive text input requires a terminal');\n }\n\n logger.info(prompt);\n logger.info('(Press Enter when done, or type Ctrl+C to cancel)');\n logger.info('');\n\n return new Promise((resolve, reject) => {\n let inputBuffer = '';\n let isResolved = false;\n let dataHandler: ((chunk: string) => void) | null = null;\n let errorHandler: ((error: Error) => void) | null = null;\n\n const cleanup = () => {\n if (dataHandler) {\n process.stdin.removeListener('data', dataHandler);\n }\n if (errorHandler) {\n process.stdin.removeListener('error', errorHandler);\n }\n\n try {\n process.stdin.pause();\n // Detach stdin again now that we're done\n if (typeof process.stdin.unref === 'function') {\n process.stdin.unref();\n }\n } catch {\n // Ignore cleanup errors\n }\n };\n\n const safeResolve = (value: string) => {\n if (!isResolved) {\n isResolved = true;\n cleanup();\n resolve(value);\n }\n };\n\n const safeReject = (error: Error) => {\n if (!isResolved) {\n isResolved = true;\n cleanup();\n reject(error);\n }\n };\n\n try {\n // Ensure stdin is referenced so the process doesn't exit while waiting for input\n if (typeof process.stdin.ref === 'function') {\n process.stdin.ref();\n }\n\n process.stdin.setEncoding('utf8');\n process.stdin.resume();\n\n dataHandler = (chunk: string) => {\n try {\n inputBuffer += chunk;\n\n // Check if user pressed Enter (newline character)\n if (inputBuffer.includes('\\n')) {\n const userInput = inputBuffer.replace(/\\n$/, '').trim();\n\n if (userInput === '') {\n logger.warn('Empty input received. Please provide feedback text.');\n safeReject(new Error('Empty input received'));\n } else {\n logger.info(`✅ Received feedback: \"${userInput}\"\\n`);\n safeResolve(userInput);\n }\n }\n } catch (error) {\n safeReject(error instanceof Error ? error : new Error('Unknown error processing input'));\n }\n };\n\n errorHandler = (error: Error) => {\n safeReject(error);\n };\n\n process.stdin.on('data', dataHandler);\n process.stdin.on('error', errorHandler);\n\n } catch (error) {\n safeReject(error instanceof Error ? error : new Error('Failed to setup input handlers'));\n }\n });\n}\n\n/**\n * Get LLM improvement feedback from the user using the editor\n * @param contentType Type of content being improved (e.g., 'commit message', 'release notes')\n * @param currentContent The current content to be improved\n * @param editor Optional editor command\n * @param logger Optional logger instance\n * @returns Promise resolving to the user's feedback text\n */\nexport async function getLLMFeedbackInEditor(\n contentType: string,\n currentContent: string,\n editor?: string,\n logger?: Logger\n): Promise<string> {\n const templateLines = [\n '# Provide Your Instructions and Guidance for a Revision Here',\n '#',\n '# Type your guidance above this line. Be specific about what you want changed,',\n '# added, or improved. You can also edit the original content below directly',\n '# to provide examples or show desired changes.',\n '#',\n '# Lines starting with \"#\" will be ignored.',\n '',\n '### YOUR FEEDBACK AND GUIDANCE:',\n '',\n '# (Type your improvement instructions here)',\n '',\n `### ORIGINAL ${contentType.toUpperCase()}:`,\n ''\n ];\n\n const result = await editContentInEditor(\n currentContent,\n templateLines,\n '.md',\n editor,\n logger\n );\n\n // Extract just the feedback section (everything before the original content)\n const lines = result.content.split('\\n');\n const originalSectionIndex = lines.findIndex(line =>\n line.trim().toLowerCase().startsWith('### original')\n );\n\n let feedback: string;\n if (originalSectionIndex >= 0) {\n // Take everything before the \"### ORIGINAL\" section\n feedback = lines.slice(0, originalSectionIndex).join('\\n').trim();\n } else {\n // If no original section found, take everything\n feedback = result.content.trim();\n }\n\n // Remove the feedback header if it exists\n feedback = feedback.replace(/^### YOUR FEEDBACK AND GUIDANCE:\\s*/i, '').trim();\n\n if (!feedback) {\n throw new Error('No feedback provided. Please provide improvement instructions.');\n }\n\n return feedback;\n}\n\n/**\n * Check if interactive mode is available (TTY check)\n * @param errorMessage Custom error message to throw if TTY not available\n * @param logger Optional logger instance\n * @throws Error if not in TTY environment\n */\nexport function requireTTY(errorMessage: string = 'Interactive mode requires a terminal. Use --dry-run instead.', logger?: Logger): void {\n const log = logger || getLogger();\n if (!process.stdin.isTTY) {\n log.error('❌ Interactive mode requires a terminal (TTY)');\n log.error(' Solutions:');\n log.error(' • Run without piping input');\n log.error(' • Use --dry-run to see the generated content');\n throw new Error(errorMessage);\n }\n}\n\n","import { Prompt, recipe } from '@riotprompt/riotprompt';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Types for the commit prompt\nexport type CommitContent = {\n diffContent: string;\n userDirection?: string;\n isFileContent?: boolean; // Flag to indicate if diffContent is actually file content\n githubIssuesContext?: string; // GitHub issues related to current version/milestone\n};\n\nexport type CommitContext = {\n logContext?: string;\n context?: string;\n directories?: string[];\n};\n\nexport type CommitConfig = {\n overridePaths?: string[];\n overrides?: boolean;\n}\n\n/**\n * Build a commit prompt using RiotPrompt Recipes.\n *\n * This prompt is configured to generate multiline commit messages by default,\n * with separate lines/bullet points for different groups of changes rather\n * than squeezing everything into single lines.\n *\n * @param config The configuration for overrides\n * @param content Mandatory content inputs (e.g. diff)\n * @param ctx Optional contextual inputs configured by the user\n */\nexport const createCommitPrompt = async (\n { overridePaths: _overridePaths, overrides: _overrides }: CommitConfig,\n { diffContent, userDirection, isFileContent, githubIssuesContext }: CommitContent,\n { logContext, context, directories }: CommitContext = {}\n): Promise<Prompt> => {\n const basePath = __dirname;\n\n // Build content items for the prompt\n const contentItems = [];\n const contextItems = [];\n\n // Developer Note: Direction is injected first as the highest-priority prompt input\n // This ensures user guidance takes precedence over other context sources like\n // GitHub issues or commit history.\n if (userDirection) {\n contentItems.push({ content: userDirection, title: 'User Direction' });\n }\n if (diffContent) {\n const contentTitle = isFileContent ? 'Project Files' : 'Diff';\n contentItems.push({ content: diffContent, title: contentTitle });\n }\n if (githubIssuesContext) {\n contentItems.push({ content: githubIssuesContext, title: 'Recent GitHub Issues' });\n }\n\n // IMPORTANT: Log context provides background but can contaminate output if too large.\n // LLMs tend to pattern-match against recent commits instead of describing the actual diff.\n // Keep messageLimit low (3-5) to minimize contamination.\n if (logContext) {\n contextItems.push({ content: logContext, title: 'Log Context' });\n }\n if (context) {\n contextItems.push({ content: context, title: 'User Context' });\n }\n if (directories && directories.length > 0) {\n contextItems.push({ directories, title: 'Directories' });\n }\n\n return recipe(basePath)\n .persona({ path: 'personas/you.md' })\n .instructions({ path: 'instructions/commit.md' })\n .overridePaths(_overridePaths ?? [])\n .overrides(_overrides ?? true)\n .content(...contentItems)\n .context(...contextItems)\n .cook();\n};\n\n","import { ContentItem, Prompt, recipe } from '@riotprompt/riotprompt';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n// Types for the release prompt\nexport type ReleaseConfig = {\n overridePaths?: string[];\n overrides?: boolean;\n}\n\nexport type ReleaseContent = {\n releaseFocus?: string;\n logContent: string;\n diffContent: string;\n milestoneIssues?: string;\n};\n\nexport type ReleaseContext = {\n context?: string;\n directories?: string[];\n};\n\nexport type ReleasePromptResult = {\n prompt: Prompt;\n maxTokens: number;\n isLargeRelease: boolean;\n};\n\n/**\n * Analyzes release content to determine if it's a large release\n * and calculates appropriate token limits\n */\nconst analyzeReleaseSize = (logContent: string, diffContent?: string, milestoneIssues?: string): { isLarge: boolean; maxTokens: number } => {\n const logLines = logContent.split('\\n').length;\n const diffLines = diffContent ? diffContent.split('\\n').length : 0;\n const milestoneLines = milestoneIssues ? milestoneIssues.split('\\n').length : 0;\n const totalContentLength = logContent.length + (diffContent?.length || 0) + (milestoneIssues?.length || 0);\n\n // Consider it a large release if:\n // - More than 20 commits (log lines typically ~3-5 per commit)\n // - More than 500 diff lines\n // - Milestone issues present (indicates significant work)\n // - Total content length > 50KB\n const isLarge = logLines > 60 || diffLines > 500 || milestoneLines > 50 || totalContentLength > 50000;\n\n if (isLarge) {\n // For large releases, significantly increase token limit\n return { isLarge: true, maxTokens: 25000 };\n } else {\n // Standard token limit for normal releases\n return { isLarge: false, maxTokens: 10000 };\n }\n};\n\n/**\n * Build a release prompt using RiotPrompt Recipes.\n */\nexport const createReleasePrompt = async (\n { overrides: _overrides, overridePaths: _overridePaths }: ReleaseConfig,\n { releaseFocus, logContent, diffContent, milestoneIssues }: ReleaseContent,\n { context, directories }: ReleaseContext = {}\n): Promise<ReleasePromptResult> => {\n const basePath = __dirname;\n\n // Analyze release size to determine token requirements\n const { isLarge: isLargeRelease, maxTokens } = analyzeReleaseSize(logContent, diffContent, milestoneIssues);\n\n // Build content items for the prompt\n const contentItems: ContentItem[] = [];\n const contextItems: ContentItem[] = [];\n\n if (diffContent) {\n contentItems.push({ content: diffContent, title: 'Diff' });\n }\n if (logContent) {\n contentItems.push({ content: logContent, title: 'Log Context' });\n }\n if (milestoneIssues) {\n contentItems.push({ content: milestoneIssues, title: 'Resolved Issues from Milestone' });\n }\n if (releaseFocus) {\n contentItems.push({ content: releaseFocus, title: 'Release Focus' });\n }\n\n // Add release size context to help guide the AI\n if (isLargeRelease) {\n contextItems.push({\n content: `This appears to be a LARGE RELEASE with significant changes. Please provide comprehensive, detailed release notes that thoroughly document all major changes, improvements, and fixes. Don't summarize - dive deep into the details.`,\n title: 'Release Size Context'\n });\n }\n\n if (context) {\n contextItems.push({ content: context, title: 'User Context' });\n }\n if (directories && directories.length > 0) {\n contextItems.push({ directories, title: 'Directories' });\n }\n\n const prompt = await recipe(basePath)\n .persona({ path: 'personas/releaser.md' })\n .instructions({ path: 'instructions/release.md' })\n .overridePaths(_overridePaths ?? [])\n .overrides(_overrides ?? true)\n .content(...contentItems)\n .context(...contextItems)\n .cook();\n\n return {\n prompt,\n maxTokens,\n isLargeRelease\n };\n};\n\n","import { ContentItem, Prompt, recipe } from '@riotprompt/riotprompt';\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nexport type ReviewConfig = {\n overridePaths?: string[];\n overrides?: boolean;\n}\n\nexport type ReviewContent = {\n notes: string;\n};\n\nexport type ReviewContext = {\n logContext?: string;\n diffContext?: string;\n releaseNotesContext?: string;\n issuesContext?: string;\n context?: string;\n directories?: string[];\n};\n\n/**\n * Build a review prompt using RiotPrompt Recipes.\n */\nexport const createReviewPrompt = async (\n { overridePaths: _overridePaths, overrides: _overrides }: ReviewConfig,\n { notes }: ReviewContent,\n { logContext, diffContext, releaseNotesContext, issuesContext, context, directories }: ReviewContext = {}\n): Promise<Prompt> => {\n const basePath = __dirname;\n\n // Build content items for the prompt\n const contentItems: ContentItem[] = [];\n const contextItems: ContentItem[] = [];\n\n if (notes) {\n contentItems.push({ content: notes, title: 'Review Notes' });\n }\n\n if (logContext) {\n contextItems.push({ content: logContext, title: 'Log Context' });\n }\n if (diffContext) {\n contextItems.push({ content: diffContext, title: 'Diff Context' });\n }\n if (releaseNotesContext) {\n contextItems.push({ content: releaseNotesContext, title: 'Release Notes Context' });\n }\n if (issuesContext) {\n contextItems.push({ content: issuesContext, title: 'Issues Context' });\n }\n if (context) {\n contextItems.push({ content: context, title: 'User Context' });\n }\n if (directories && directories.length > 0) {\n contextItems.push({ directories, title: 'Directories' });\n }\n\n return recipe(basePath)\n .persona({ path: 'personas/you.md' })\n .instructions({ path: 'instructions/review.md' })\n .overridePaths(_overridePaths ?? [])\n .overrides(_overrides ?? true)\n .content(...contentItems)\n .context(...contextItems)\n .cook();\n};\n\n"],"names":["isTokenLimitError","logger","timeoutMs","fs","__filename","__dirname","path"],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { ChatCompletionMessageParam } from 'openai/resources';
2
+ import { AIConfig, Transcription, StorageAdapter, Logger } from './types';
3
+ export interface OpenAIOptions {
4
+ responseFormat?: any;
5
+ model?: string;
6
+ debug?: boolean;
7
+ debugFile?: string;
8
+ debugRequestFile?: string;
9
+ debugResponseFile?: string;
10
+ maxTokens?: number;
11
+ openaiReasoning?: 'low' | 'medium' | 'high';
12
+ openaiMaxOutputTokens?: number;
13
+ storage?: StorageAdapter;
14
+ logger?: Logger;
15
+ }
16
+ export interface TranscriptionOptions {
17
+ model?: string;
18
+ debug?: boolean;
19
+ debugFile?: string;
20
+ debugRequestFile?: string;
21
+ debugResponseFile?: string;
22
+ outputDirectory?: string;
23
+ storage?: StorageAdapter;
24
+ logger?: Logger;
25
+ onArchive?: (audioPath: string, transcriptionText: string) => Promise<void>;
26
+ }
27
+ /**
28
+ * Get the appropriate model to use based on command-specific configuration
29
+ * Command-specific model overrides the global model setting
30
+ */
31
+ export declare function getModelForCommand(config: AIConfig, commandName: string): string;
32
+ /**
33
+ * Get the appropriate OpenAI reasoning level based on command-specific configuration
34
+ * Command-specific reasoning overrides the global reasoning setting
35
+ */
36
+ export declare function getOpenAIReasoningForCommand(config: AIConfig, commandName: string): 'low' | 'medium' | 'high';
37
+ export declare class OpenAIError extends Error {
38
+ readonly isTokenLimitError: boolean;
39
+ constructor(message: string, isTokenLimitError?: boolean);
40
+ }
41
+ export declare function isTokenLimitError(error: any): boolean;
42
+ export declare function isRateLimitError(error: any): boolean;
43
+ /**
44
+ * Create OpenAI completion with optional debug and retry support
45
+ */
46
+ export declare function createCompletion(messages: ChatCompletionMessageParam[], options?: OpenAIOptions): Promise<string | any>;
47
+ /**
48
+ * Create completion with automatic retry on token limit errors
49
+ */
50
+ export declare function createCompletionWithRetry(messages: ChatCompletionMessageParam[], options?: OpenAIOptions, retryCallback?: (attempt: number) => Promise<ChatCompletionMessageParam[]>): Promise<string | any>;
51
+ /**
52
+ * Transcribe audio file using OpenAI Whisper API
53
+ */
54
+ export declare function transcribeAudio(filePath: string, options?: TranscriptionOptions): Promise<Transcription>;
55
+ //# sourceMappingURL=ai.d.ts.map
@@ -5,10 +5,9 @@
5
5
  * Provides OpenAI integration with structured prompts for
6
6
  * generating commit messages, release notes, and code reviews.
7
7
  */
8
-
9
- // Core functionality
10
8
  export * from './types';
11
9
  export * from './logger';
12
10
  export * from './ai';
13
11
  export * from './interactive';
14
12
  export * from './prompts';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,122 @@
1
+ import { Logger, Choice, InteractiveOptions } from './types';
2
+ /**
3
+ * Get user choice interactively from terminal input
4
+ * @param prompt The prompt message to display
5
+ * @param choices Array of available choices
6
+ * @param options Additional options for customizing behavior
7
+ * @returns Promise resolving to the selected choice key
8
+ */
9
+ export declare function getUserChoice(prompt: string, choices: Choice[], options?: InteractiveOptions): Promise<string>;
10
+ /**
11
+ * Secure temporary file handle that prevents TOCTOU vulnerabilities
12
+ */
13
+ export declare class SecureTempFile {
14
+ private fd;
15
+ private filePath;
16
+ private isCleanedUp;
17
+ private logger;
18
+ private constructor();
19
+ /**
20
+ * Create a secure temporary file with proper permissions and atomic operations
21
+ * @param prefix Prefix for the temporary filename
22
+ * @param extension File extension (e.g., '.txt', '.md')
23
+ * @param logger Optional logger instance
24
+ * @returns Promise resolving to SecureTempFile instance
25
+ */
26
+ static create(prefix?: string, extension?: string, logger?: Logger): Promise<SecureTempFile>;
27
+ /**
28
+ * Get the file path (use with caution in external commands)
29
+ */
30
+ get path(): string;
31
+ /**
32
+ * Write content to the temporary file
33
+ */
34
+ writeContent(content: string): Promise<void>;
35
+ /**
36
+ * Read content from the temporary file
37
+ */
38
+ readContent(): Promise<string>;
39
+ /**
40
+ * Close the file handle
41
+ */
42
+ close(): Promise<void>;
43
+ /**
44
+ * Securely cleanup the temporary file - prevents TOCTOU by using file descriptor
45
+ */
46
+ cleanup(): Promise<void>;
47
+ }
48
+ /**
49
+ * Create a secure temporary file for editing with proper permissions
50
+ * @param prefix Prefix for the temporary filename
51
+ * @param extension File extension (e.g., '.txt', '.md')
52
+ * @param logger Optional logger instance
53
+ * @returns Promise resolving to the temporary file path
54
+ * @deprecated Use SecureTempFile.create() for better security
55
+ */
56
+ export declare function createSecureTempFile(prefix?: string, extension?: string, logger?: Logger): Promise<string>;
57
+ /**
58
+ * Clean up a temporary file
59
+ * @param filePath Path to the temporary file to clean up
60
+ * @param logger Optional logger instance
61
+ * @deprecated Use SecureTempFile.cleanup() for better security
62
+ */
63
+ export declare function cleanupTempFile(filePath: string, logger?: Logger): Promise<void>;
64
+ export interface EditorResult {
65
+ content: string;
66
+ wasEdited: boolean;
67
+ }
68
+ /**
69
+ * Open content in user's editor for editing
70
+ * @param content Initial content to edit
71
+ * @param templateLines Additional template lines to include (will be filtered out)
72
+ * @param fileExtension File extension for syntax highlighting
73
+ * @param editor Editor command to use (defaults to EDITOR/VISUAL env var or 'vi')
74
+ * @param logger Optional logger instance
75
+ * @returns Promise resolving to the edited content
76
+ */
77
+ export declare function editContentInEditor(content: string, templateLines?: string[], fileExtension?: string, editor?: string, logger?: Logger): Promise<EditorResult>;
78
+ /**
79
+ * Standard choices for interactive feedback loops
80
+ */
81
+ export declare const STANDARD_CHOICES: {
82
+ readonly CONFIRM: {
83
+ readonly key: "c";
84
+ readonly label: "Confirm and proceed";
85
+ };
86
+ readonly EDIT: {
87
+ readonly key: "e";
88
+ readonly label: "Edit in editor";
89
+ };
90
+ readonly SKIP: {
91
+ readonly key: "s";
92
+ readonly label: "Skip and abort";
93
+ };
94
+ readonly IMPROVE: {
95
+ readonly key: "i";
96
+ readonly label: "Improve with LLM feedback";
97
+ };
98
+ };
99
+ /**
100
+ * Get text input from the user
101
+ * @param prompt The prompt message to display
102
+ * @param options Additional options for customizing behavior
103
+ * @returns Promise resolving to the user's text input
104
+ */
105
+ export declare function getUserTextInput(prompt: string, options?: InteractiveOptions): Promise<string>;
106
+ /**
107
+ * Get LLM improvement feedback from the user using the editor
108
+ * @param contentType Type of content being improved (e.g., 'commit message', 'release notes')
109
+ * @param currentContent The current content to be improved
110
+ * @param editor Optional editor command
111
+ * @param logger Optional logger instance
112
+ * @returns Promise resolving to the user's feedback text
113
+ */
114
+ export declare function getLLMFeedbackInEditor(contentType: string, currentContent: string, editor?: string, logger?: Logger): Promise<string>;
115
+ /**
116
+ * Check if interactive mode is available (TTY check)
117
+ * @param errorMessage Custom error message to throw if TTY not available
118
+ * @param logger Optional logger instance
119
+ * @throws Error if not in TTY environment
120
+ */
121
+ export declare function requireTTY(errorMessage?: string, logger?: Logger): void;
122
+ //# sourceMappingURL=interactive.d.ts.map
@@ -0,0 +1,19 @@
1
+ import { Logger } from './types';
2
+ /**
3
+ * Set a custom logger instance
4
+ */
5
+ export declare function setLogger(customLogger: Logger): void;
6
+ /**
7
+ * Create a no-op logger that does nothing
8
+ */
9
+ export declare function createNoOpLogger(): Logger;
10
+ /**
11
+ * Attempt to load winston logger
12
+ * @returns winston logger if available, otherwise null
13
+ */
14
+ export declare function tryLoadWinston(): Logger | null;
15
+ /**
16
+ * Get the current logger or a no-op logger
17
+ */
18
+ export declare function getLogger(): Logger;
19
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1,29 @@
1
+ import { Prompt } from '@riotprompt/riotprompt';
2
+ export type CommitContent = {
3
+ diffContent: string;
4
+ userDirection?: string;
5
+ isFileContent?: boolean;
6
+ githubIssuesContext?: string;
7
+ };
8
+ export type CommitContext = {
9
+ logContext?: string;
10
+ context?: string;
11
+ directories?: string[];
12
+ };
13
+ export type CommitConfig = {
14
+ overridePaths?: string[];
15
+ overrides?: boolean;
16
+ };
17
+ /**
18
+ * Build a commit prompt using RiotPrompt Recipes.
19
+ *
20
+ * This prompt is configured to generate multiline commit messages by default,
21
+ * with separate lines/bullet points for different groups of changes rather
22
+ * than squeezing everything into single lines.
23
+ *
24
+ * @param config The configuration for overrides
25
+ * @param content Mandatory content inputs (e.g. diff)
26
+ * @param ctx Optional contextual inputs configured by the user
27
+ */
28
+ export declare const createCommitPrompt: ({ overridePaths: _overridePaths, overrides: _overrides }: CommitConfig, { diffContent, userDirection, isFileContent, githubIssuesContext }: CommitContent, { logContext, context, directories }?: CommitContext) => Promise<Prompt>;
29
+ //# sourceMappingURL=commit.d.ts.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Structured prompt builders for AI content generation
3
+ */
4
+ export * from './commit';
5
+ export * from './release';
6
+ export * from './review';
7
+ export type { CommitContent, CommitContext, CommitConfig, } from './commit';
8
+ export type { ReleaseContent, ReleaseContext, ReleaseConfig, ReleasePromptResult, } from './release';
9
+ export type { ReviewContent, ReviewContext, ReviewConfig, } from './review';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { Prompt } from '@riotprompt/riotprompt';
2
+ export type ReleaseConfig = {
3
+ overridePaths?: string[];
4
+ overrides?: boolean;
5
+ };
6
+ export type ReleaseContent = {
7
+ releaseFocus?: string;
8
+ logContent: string;
9
+ diffContent: string;
10
+ milestoneIssues?: string;
11
+ };
12
+ export type ReleaseContext = {
13
+ context?: string;
14
+ directories?: string[];
15
+ };
16
+ export type ReleasePromptResult = {
17
+ prompt: Prompt;
18
+ maxTokens: number;
19
+ isLargeRelease: boolean;
20
+ };
21
+ /**
22
+ * Build a release prompt using RiotPrompt Recipes.
23
+ */
24
+ export declare const createReleasePrompt: ({ overrides: _overrides, overridePaths: _overridePaths }: ReleaseConfig, { releaseFocus, logContent, diffContent, milestoneIssues }: ReleaseContent, { context, directories }?: ReleaseContext) => Promise<ReleasePromptResult>;
25
+ //# sourceMappingURL=release.d.ts.map
@@ -0,0 +1,21 @@
1
+ import { Prompt } from '@riotprompt/riotprompt';
2
+ export type ReviewConfig = {
3
+ overridePaths?: string[];
4
+ overrides?: boolean;
5
+ };
6
+ export type ReviewContent = {
7
+ notes: string;
8
+ };
9
+ export type ReviewContext = {
10
+ logContext?: string;
11
+ diffContext?: string;
12
+ releaseNotesContext?: string;
13
+ issuesContext?: string;
14
+ context?: string;
15
+ directories?: string[];
16
+ };
17
+ /**
18
+ * Build a review prompt using RiotPrompt Recipes.
19
+ */
20
+ export declare const createReviewPrompt: ({ overridePaths: _overridePaths, overrides: _overrides }: ReviewConfig, { notes }: ReviewContent, { logContext, diffContext, releaseNotesContext, issuesContext, context, directories }?: ReviewContext) => Promise<Prompt>;
21
+ //# sourceMappingURL=review.d.ts.map
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Type definitions for AI service
3
+ */
4
+ /**
5
+ * AI model reasoning effort levels
6
+ */
7
+ export type ReasoningLevel = 'low' | 'medium' | 'high';
8
+ /**
9
+ * Configuration for AI operations
10
+ */
11
+ export interface AIConfig {
12
+ /** OpenAI API key */
13
+ apiKey?: string;
14
+ /** Model to use (e.g., 'gpt-4o-mini', 'gpt-4o') */
15
+ model?: string;
16
+ /** Reasoning effort level */
17
+ reasoning?: ReasoningLevel;
18
+ /** Command-specific configurations */
19
+ commands?: {
20
+ commit?: {
21
+ model?: string;
22
+ reasoning?: ReasoningLevel;
23
+ };
24
+ release?: {
25
+ model?: string;
26
+ reasoning?: ReasoningLevel;
27
+ };
28
+ review?: {
29
+ model?: string;
30
+ reasoning?: ReasoningLevel;
31
+ };
32
+ };
33
+ }
34
+ /**
35
+ * Result from AI transcription
36
+ */
37
+ export interface Transcription {
38
+ text: string;
39
+ }
40
+ /**
41
+ * Storage interface for file operations
42
+ * Consumers can provide their own implementation
43
+ */
44
+ export interface StorageAdapter {
45
+ writeOutput(fileName: string, content: string): Promise<void>;
46
+ readTemp(fileName: string): Promise<string>;
47
+ writeTemp(fileName: string, content: string): Promise<void>;
48
+ }
49
+ /**
50
+ * Logger interface for optional logging
51
+ * Compatible with winston but not required
52
+ */
53
+ export interface Logger {
54
+ info(message: string, ...meta: unknown[]): void;
55
+ error(message: string, ...meta: unknown[]): void;
56
+ warn(message: string, ...meta: unknown[]): void;
57
+ debug(message: string, ...meta: unknown[]): void;
58
+ }
59
+ /**
60
+ * Choice for interactive prompts
61
+ */
62
+ export interface Choice {
63
+ key: string;
64
+ label: string;
65
+ }
66
+ /**
67
+ * Options for interactive prompts
68
+ */
69
+ export interface InteractiveOptions {
70
+ nonTtyErrorSuggestions?: string[];
71
+ logger?: Logger;
72
+ }
73
+ /**
74
+ * Options for editor integration
75
+ */
76
+ export interface EditorOptions {
77
+ editor?: string;
78
+ tempDir?: string;
79
+ extension?: string;
80
+ logger?: Logger;
81
+ }
82
+ /**
83
+ * Options for LLM feedback loop
84
+ */
85
+ export interface FeedbackOptions {
86
+ initialContent: string;
87
+ systemMessage: string;
88
+ aiConfig: AIConfig;
89
+ commandName: string;
90
+ storage?: StorageAdapter;
91
+ outputPrefix?: string;
92
+ editor?: EditorOptions;
93
+ logger?: Logger;
94
+ }
95
+ /**
96
+ * Re-export Prompt type from riotprompt for convenience
97
+ */
98
+ export type { Prompt, ContentItem } from '@riotprompt/riotprompt';
99
+ //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eldrforge/ai-service",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "AI-powered content generation for automation - OpenAI integration with structured prompts",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -10,6 +10,9 @@
10
10
  "types": "./dist/index.d.ts"
11
11
  }
12
12
  },
13
+ "files": [
14
+ "dist"
15
+ ],
13
16
  "repository": {
14
17
  "type": "git",
15
18
  "url": "git+https://github.com/calenvarek/ai-service.git"
@@ -39,7 +42,7 @@
39
42
  "author": "Calen Varek <calenvarek@gmail.com>",
40
43
  "license": "Apache-2.0",
41
44
  "dependencies": {
42
- "@eldrforge/git-tools": "^0.1.3",
45
+ "@eldrforge/git-tools": "^0.1.5",
43
46
  "@riotprompt/riotprompt": "^0.0.8",
44
47
  "openai": "^6.3.0"
45
48
  },
@@ -55,21 +58,21 @@
55
58
  "@eslint/eslintrc": "^3.3.1",
56
59
  "@eslint/js": "^9.33.0",
57
60
  "@swc/core": "^1.13.3",
58
- "@types/node": "^24.2.1",
61
+ "@types/node": "^24.10.1",
59
62
  "@types/winston": "^2.4.4",
60
63
  "@typescript-eslint/eslint-plugin": "^8.39.1",
61
- "@typescript-eslint/parser": "^8.39.1",
62
- "@vitest/coverage-v8": "^3.2.4",
63
- "esbuild": "0.25.10",
64
+ "@typescript-eslint/parser": "^8.47.0",
65
+ "@vitest/coverage-v8": "^4.0.13",
66
+ "esbuild": "0.27.0",
64
67
  "eslint": "^9.33.0",
65
68
  "eslint-plugin-import": "^2.32.0",
66
69
  "globals": "^16.3.0",
67
70
  "mockdate": "^3.0.5",
68
71
  "typescript": "^5.9.2",
69
- "vite": "^7.1.2",
72
+ "vite": "^7.2.4",
70
73
  "vite-plugin-dts": "^4.3.0",
71
74
  "vite-plugin-node": "^7.0.0",
72
- "vitest": "^3.2.4",
75
+ "vitest": "^4.0.13",
73
76
  "winston": "^3.17.0"
74
77
  }
75
78
  }
@@ -1,12 +0,0 @@
1
- # To get started with Dependabot version updates, you'll need to specify which
2
- # package ecosystems to update and where the package manifests are located.
3
- # Please see the documentation for all configuration options:
4
- # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
-
6
- version: 2
7
- updates:
8
- - package-ecosystem: "npm" # See documentation for possible values
9
- directory: "/" # Location of package manifests
10
- schedule:
11
- interval: "weekly"
12
-