{"version":3,"sources":["../src/coolhand.ts"],"sourcesContent":["import { CoolhandOptions, CoolhandCallData, CoolhandLogResponse, CoolhandStats, LLMRequestLogFeedback, LLMRequestLogFeedbackResponse, CoolhandMatchedPattern, SearchFeedbackParams, SearchFeedbackResponse, LLMRequestLogFeedbackDetail, GetLogContentOptions, GetLogContentSliceOptions, GetLogContentSearchOptions, LlmRequestLogContent, LlmRequestLogContentFull, LlmRequestLogContentSearchResult, SearchLogsParams, SearchLogsResponse, CoolhandClientFilePayload, CoolhandClientFileResponse } from './types.js';\nimport { PatternMatchingService } from './services/PatternMatchingService.js';\nimport { RequestMonitoringService } from './services/RequestMonitoringService.js';\nimport { LoggingService } from './services/LoggingService.js';\nimport { FeedbackService } from './services/FeedbackService.js';\nimport { ClientFileService } from './services/ClientFileService.js';\nimport { DEFAULT_EXCLUDE_API_PATTERNS } from './default-exclude-api-patterns.js';\n\nexport class Coolhand {\n  private patternMatchingService: PatternMatchingService;\n  private requestMonitoringService: RequestMonitoringService;\n  private loggingService: LoggingService;\n  private feedbackService: FeedbackService;\n  private clientFileService: ClientFileService;\n  private silent: boolean;\n\n  constructor(options: CoolhandOptions) {\n    // Configuration options\n    this.silent = options.silent !== false;\n    const apiKey = options.apiKey;\n\n    if (!apiKey) {\n      console.error('❌ API key is required for logging. Pass it in options.apiKey');\n      throw new Error('API key is required');\n    }\n\n    // TODO: remove after v1.x.x — backward-compat shim for deprecated `environment` option\n    if (options.environment !== undefined) {\n      console.warn(\n        '[coolhand-node] DEPRECATION WARNING: The `environment` option was removed in v0.4.0. ' +\n        \"Use `baseUrl: 'http://localhost:3000'` instead of `environment: 'local'`. \" +\n        'Remove `environment: \\'production\\'` — the default endpoint is unchanged.'\n      );\n      if (options.environment === 'local' && options.baseUrl === undefined) {\n        options = { ...options, baseUrl: 'http://localhost:3000' };\n      }\n    }\n\n    // Initialize services\n    this.patternMatchingService = new PatternMatchingService({ customPatternsFile: options.patternsFile, silent: this.silent });\n\n    const serviceConfig = {\n      apiKey,\n      silent: this.silent,\n      debug: options.debug,\n      dryRun: options.dryRun,\n      baseUrl: options.baseUrl\n    };\n\n    this.loggingService = new LoggingService(serviceConfig);\n    this.feedbackService = new FeedbackService(serviceConfig);\n    this.clientFileService = new ClientFileService(serviceConfig);\n    this.requestMonitoringService = new RequestMonitoringService(this.patternMatchingService, this.silent);\n    this.requestMonitoringService.excludeApiPatterns = [...(options.excludeApiPatterns ?? DEFAULT_EXCLUDE_API_PATTERNS)];\n\n    // Set up the callback for when requests are completed\n    this.requestMonitoringService.onRequestComplete = (callData: CoolhandCallData, matchedPattern?: CoolhandMatchedPattern) => {\n      this.loggingService.logRequestToAPI(callData, matchedPattern, 'manual');\n    };\n\n    if (options.debug && !options.dryRun) {\n      console.warn(\n        '[coolhand-node] DEPRECATION WARNING: `debug: true` no longer suppresses API calls. ' +\n        'Use `dryRun: true` to prevent data submission. ' +\n        '`debug` now only enables verbose logging.'\n      );\n    }\n\n    if (!this.silent) {\n      console.log('🔍 Setting up Coolhand...');\n      if (options.dryRun) {\n        console.log('🚫 DRY RUN MODE: API calls will be skipped — no data will be submitted');\n      }\n      if (options.debug) {\n        console.log('🔬 DEBUG MODE: Verbose logging enabled');\n      }\n      console.log(`🎯 API Endpoint: ${this.loggingService.getApiEndpoint()}`);\n    }\n\n    this.requestMonitoringService.setupMonitoring();\n\n    if (!this.silent) {\n      console.log('✅ Coolhand ready - will log to API');\n    }\n  }\n\n  // Public API methods\n\n  /**\n   * Manually submit a single captured LLM request/response to Coolhand.\n   *\n   * Use this for logs that did not flow through automatic monitoring — for example the\n   * coolhand-cli `capture-sessions` tool submitting locally-saved Claude Code / Codex\n   * session turns.\n   *\n   * @param rawRequest The captured request/response payload.\n   * @param options Optional settings. `collector` identifies the submission source and\n   *   overrides the default SDK collector string. `metadata` is a free-form object; the one\n   *   convention the backend uses is `project_path` (e.g. `{ project_path: '/Users/me/my-project' }`\n   *   — see the Understanding an LLM Request Log guide's Metadata section).\n   * @returns Promise resolving to the created log response, or null if submission failed\n   *   or if the request was sent via the HTTPS fallback (Node.js < 18, where the response\n   *   body is not parsed).\n   */\n  public async logRequest(\n    rawRequest: CoolhandCallData,\n    options?: { collector?: string; metadata?: Record<string, unknown> }\n  ): Promise<CoolhandLogResponse | null> {\n    return this.loggingService.logRequestToAPI(rawRequest, undefined, 'manual', options?.collector, options?.metadata);\n  }\n\n  /**\n   * Create feedback for an LLM request log\n   * @param feedback The feedback data\n   * @returns Promise resolving to the created feedback response or null if failed\n   */\n  public async createFeedback(feedback: LLMRequestLogFeedback): Promise<LLMRequestLogFeedbackResponse | null> {\n    return this.feedbackService.createFeedback(feedback, 'manual');\n  }\n\n  /**\n   * Upload a file (slide deck, report, or document) to Coolhand.\n   *\n   * Requires the **private** API key — construct this `Coolhand` instance with `apiKey` set to\n   * your private key, not the public key used for `createFeedback`/`logRequest`, which 401s here.\n   *\n   * Uploads always land with `status: draft` — `status` is not settable via this method.\n   * Requires Node.js 18+ (uses global `fetch`/`FormData`; there is no fallback for pre-18 Node).\n   *\n   * @param payload The file to upload plus optional `file_type`, `description`, and `metadata`.\n   * @returns Promise resolving to the created client file response, or null if the upload failed.\n   */\n  public async uploadClientFile(payload: CoolhandClientFilePayload): Promise<CoolhandClientFileResponse | null> {\n    return this.clientFileService.createClientFile(payload);\n  }\n\n  /**\n   * Search feedback records using raw Ransack predicates (`q[...]` keys) plus `page`/`per`.\n   *\n   * Requires the **private** API key — construct this `Coolhand` instance with `apiKey` set to\n   * your private key, not the public key used for `createFeedback`/`logRequest`, which 401s here.\n   *\n   * @param params Ransack predicates (e.g. `sentiment_eq`, `explanation_cont`), `s` (sort), and\n   *   `page`/`per` (pagination).\n   * @returns The matching feedback records (`:summary` view) plus pagination metadata.\n   * @throws Error on network failure or a non-JSON body. A non-2xx response throws an error\n   *   whose `status` property holds the HTTP status code.\n   */\n  public async searchFeedback(params?: SearchFeedbackParams): Promise<SearchFeedbackResponse> {\n    return this.feedbackService.searchFeedback(params);\n  }\n\n  /**\n   * Get a single feedback record by ID, including `original_output`/`revised_output`/\n   * `feedback_partials`.\n   *\n   * Requires the **private** API key, same as {@link searchFeedback}.\n   *\n   * @param id The feedback record ID.\n   * @returns The full feedback record (`:with_partials` view).\n   * @throws Error if `id` is blank/whitespace-only or a bare dot-segment (`.`/`..`). Error on\n   *   network failure or a non-JSON body. A non-2xx response throws an error whose `status`\n   *   property holds the HTTP status code (e.g. 404 for an unknown ID).\n   */\n  public async getFeedback(id: string): Promise<LLMRequestLogFeedbackDetail> {\n    return this.feedbackService.getFeedback(id);\n  }\n\n  /**\n   * Fetch full input/output content for a single log by ID.\n   *\n   * Requires the **private** API key — construct this `Coolhand` instance with `apiKey` set to\n   * your private key, not the public key used for `createFeedback`/`logRequest`, which 401s here.\n   *\n   * @param logId The log's hashid.\n   * @param opts `section`/`maxChars` for large logs, or `searchQuery` for snippet search\n   *   (mutually exclusive with `section`/`maxChars` — enforced by the overloads below), plus\n   *   `includeThinking`.\n   * @throws Error if `logId` is blank/whitespace-only or a bare dot-segment (`.`/`..`), or if\n   *   `searchQuery` is blank/whitespace-only. Error on network failure or a non-JSON body. A\n   *   non-2xx response throws an error whose `status` property holds the HTTP status code (e.g.\n   *   404 for an unknown ID).\n   */\n  public async getLogContent(logId: string, opts?: GetLogContentSliceOptions): Promise<LlmRequestLogContentFull>;\n  public async getLogContent(logId: string, opts: GetLogContentSearchOptions): Promise<LlmRequestLogContentSearchResult>;\n  public async getLogContent(logId: string, opts: GetLogContentOptions): Promise<LlmRequestLogContent>;\n  public async getLogContent(logId: string, opts?: GetLogContentOptions): Promise<LlmRequestLogContent> {\n    return this.loggingService.getLogContent(logId, opts ?? {});\n  }\n\n  /**\n   * Search logs by named filters (`templateId`, `workloadId`, `model`, etc.) — not raw Ransack\n   * predicates, unlike {@link searchFeedback}.\n   *\n   * Requires the **private** API key, same as {@link getLogContent}.\n   *\n   * @returns `{ logs, pagination }` — the matching logs for the requested page, plus pagination\n   *   totals. See `LoggingService#searchLogs`/`docs/log-search.md` for how `pagination` is sourced.\n   * @throws Error on network failure or a non-JSON body. A non-2xx response throws an error\n   *   whose `status` property holds the HTTP status code.\n   */\n  public async searchLogs(params?: SearchLogsParams): Promise<SearchLogsResponse> {\n    return this.loggingService.searchLogs(params);\n  }\n\n  /**\n   * Get sanitized headers for debugging purposes\n   * @param headers Headers to sanitize\n   * @param pattern Optional API pattern for pattern-specific sanitization\n   * @returns Sanitized headers\n   */\n  public sanitizeHeaders(headers: any, pattern?: any): Record<string, any> {\n    return this.patternMatchingService.sanitizeHeaders(headers, pattern);\n  }\n\n  /**\n   * Get monitoring statistics\n   * @returns Statistics about requests and interceptions\n   */\n  public getStats(): CoolhandStats {\n    const monitoringStats = this.requestMonitoringService.getStats();\n    return {\n      totalRequests: monitoringStats.totalRequests,\n      interceptedCalls: monitoringStats.interceptedCalls,\n      apiEndpoint: this.loggingService.getApiEndpoint()\n    };\n  }\n\n  public get excludeApiPatterns(): string[] {\n    return this.requestMonitoringService.excludeApiPatterns;\n  }\n\n  public set excludeApiPatterns(patterns: string[]) {\n    this.requestMonitoringService.excludeApiPatterns = [...patterns];\n  }\n}"],"mappings":"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","names":[]}