{"version":3,"file":"piConfig-l5YPuZOt.cjs","names":["path","os","fs"],"sources":["../src/services/imageSettings/index.ts","../src/services/piConfig/index.ts"],"sourcesContent":["/**\n * The image limits every DoomPi surface applies before an image reaches a model.\n *\n * Pi already owns half of this: `images.autoResize` lives in its settings.json\n * and its resize core hardcodes a 2000px cap. What Pi has no place for is a\n * smaller cap, so `images.maxDimension` is Doom's key written into the same\n * file, because a machine should describe how images reach a model in one\n * place rather than two.\n *\n * A cap above Pi's own is not offered. Pi normalizes every tool result image at\n * 2000px as it enters session history, so a larger number would hold on a\n * pasted image and be silently undone on a read. Clamping says so instead of\n * letting the two surfaces disagree.\n *\n * AVOID:\n * - Reading these keys straight off a config record; a hand-written file holds\n *   strings, nulls and negative numbers, and only this parser answers for them.\n */\n\nimport type { PiConfig } from '../../types/piConfig';\n\nexport const IMAGE_SETTINGS_KEY = 'images';\nexport const IMAGE_AUTO_RESIZE_KEY = 'autoResize';\nexport const IMAGE_MAX_DIMENSION_KEY = 'maxDimension';\n\n/** Pi's own cap, from its resize core; also the largest value worth writing. */\nexport const DEFAULT_IMAGE_MAX_DIMENSION = 2000;\n/** Below this a screenshot stops being readable, which is what reads are for. */\nexport const MIN_IMAGE_MAX_DIMENSION = 256;\n\nexport interface PiImageSettings {\n  /** Pi's own toggle. Unset is on, matching Pi's default. */\n  autoResize: boolean;\n  /** The longest edge an image keeps once resizing is on. */\n  maxDimension: number;\n}\n\n/** The written form of a change; an absent field leaves that key alone. */\nexport interface PiImageSettingsUpdate {\n  autoResize?: boolean;\n  maxDimension?: number;\n}\n\nexport function clampImageMaxDimension(value: number): number {\n  const rounded = Math.round(value);\n  if (rounded < MIN_IMAGE_MAX_DIMENSION) return MIN_IMAGE_MAX_DIMENSION;\n  if (rounded > DEFAULT_IMAGE_MAX_DIMENSION) return DEFAULT_IMAGE_MAX_DIMENSION;\n  return rounded;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Resolves the effective limits, defaulting anything absent or unusable. */\nexport function parsePiImageSettings(config: PiConfig): PiImageSettings {\n  const images = config[IMAGE_SETTINGS_KEY];\n  if (!isRecord(images)) return { autoResize: true, maxDimension: DEFAULT_IMAGE_MAX_DIMENSION };\n  const autoResize = images[IMAGE_AUTO_RESIZE_KEY];\n  const maxDimension = images[IMAGE_MAX_DIMENSION_KEY];\n  return {\n    autoResize: typeof autoResize === 'boolean' ? autoResize : true,\n    maxDimension:\n      typeof maxDimension === 'number' && Number.isFinite(maxDimension)\n        ? clampImageMaxDimension(maxDimension)\n        : DEFAULT_IMAGE_MAX_DIMENSION,\n  };\n}\n\n/**\n * Folds a change into the `images` object of a settings document, leaving every\n * other key of that document, and of `images` itself, as it was.\n */\nexport function applyPiImageSettings(config: PiConfig, update: PiImageSettingsUpdate): PiConfig {\n  const existing = config[IMAGE_SETTINGS_KEY];\n  const images: Record<string, unknown> = isRecord(existing) ? { ...existing } : {};\n  if (update.autoResize !== undefined) images[IMAGE_AUTO_RESIZE_KEY] = update.autoResize;\n  if (update.maxDimension !== undefined) images[IMAGE_MAX_DIMENSION_KEY] = clampImageMaxDimension(update.maxDimension);\n  return { ...config, [IMAGE_SETTINGS_KEY]: images };\n}\n","import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport type { PiConfig, PiConfigLoadOptions, PiConfigPaths } from '../../types/piConfig';\nimport { writePrivateAtomicJson } from '../atomicJson';\nimport {\n  applyPiImageSettings,\n  parsePiImageSettings,\n  type PiImageSettings,\n  type PiImageSettingsUpdate,\n} from '../imageSettings';\n\nconst SETTINGS_FILE_NAME = 'settings.json';\nconst CONFIG_DIR_NAME = '.pi';\nconst AGENT_DIR_NAME = 'agent';\nconst PI_CODING_AGENT_DIR_ENV = 'PI_CODING_AGENT_DIR';\nconst LEGACY_CONFIG_DIR_NAME = 'pi';\n\nfunction defaultAgentDirectory(homeDirectory: string): string {\n  const fallback = path.join(homeDirectory, CONFIG_DIR_NAME, AGENT_DIR_NAME);\n  if (homeDirectory !== os.homedir()) return fallback;\n  const configured = process.env[PI_CODING_AGENT_DIR_ENV]?.trim();\n  if (!configured) return fallback;\n  if (configured === '~') return homeDirectory;\n  if (configured.startsWith('~/')) return path.join(homeDirectory, configured.slice(2));\n  return path.resolve(configured);\n}\nfunction isObject(value: unknown): value is PiConfig {\n  return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction mergeConfig(base: PiConfig, override: PiConfig): PiConfig {\n  const result: PiConfig = { ...base };\n  for (const [key, value] of Object.entries(override)) {\n    const current = result[key];\n    result[key] = isObject(current) && isObject(value) ? mergeConfig(current, value) : value;\n  }\n  return result;\n}\n\nfunction readConfig(filePath: string): PiConfig {\n  if (!fs.existsSync(filePath)) return {};\n  let parsed: unknown;\n  try {\n    parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));\n  } catch (error) {\n    throw new Error(`Could not parse Pi settings at ${filePath}`, { cause: error });\n  }\n  if (!isObject(parsed)) throw new Error(`Pi settings at ${filePath} must be a JSON object`);\n  return parsed;\n}\n\nasync function readConfigAsync(filePath: string): Promise<PiConfig> {\n  let parsed: unknown;\n  try {\n    parsed = JSON.parse(await fs.promises.readFile(filePath, 'utf8'));\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code === 'ENOENT') return {};\n    throw new Error(`Could not parse Pi settings at ${filePath}`, { cause: error });\n  }\n  if (!isObject(parsed)) throw new Error(`Pi settings at ${filePath} must be a JSON object`);\n  return parsed;\n}\n\nfunction readConfigPaths(paths: readonly string[]): PiConfig {\n  return paths.reduce<PiConfig>((config, filePath) => mergeConfig(config, readConfig(filePath)), {});\n}\n\nasync function readConfigPathsAsync(paths: readonly string[]): Promise<PiConfig> {\n  const layers = await Promise.all(paths.map(readConfigAsync));\n  return layers.reduce<PiConfig>(mergeConfig, {});\n}\n\nfunction projectIsTrusted(value: PiConfigLoadOptions['isProjectTrusted']): boolean {\n  return typeof value === 'function' ? value() : value;\n}\n\nexport function piConfigPaths(\n  repoRoot: string,\n  homeDirectory = os.homedir(),\n  agentDirectory = defaultAgentDirectory(homeDirectory),\n): PiConfigPaths {\n  return {\n    canonicalUser: path.join(agentDirectory, SETTINGS_FILE_NAME),\n    canonicalProject: path.join(repoRoot, CONFIG_DIR_NAME, SETTINGS_FILE_NAME),\n    legacyUser: [path.join(homeDirectory, CONFIG_DIR_NAME, SETTINGS_FILE_NAME)],\n    legacyProject: [path.join(repoRoot, LEGACY_CONFIG_DIR_NAME, SETTINGS_FILE_NAME)],\n  };\n}\n\nexport function loadPiConfig(options: PiConfigLoadOptions): PiConfig {\n  const paths = piConfigPaths(options.repoRoot, options.homeDirectory);\n  const userPaths = [...paths.legacyUser, ...(options.userConfigPaths ?? []), paths.canonicalUser];\n  const projectPaths = [...paths.legacyProject, ...(options.projectConfigPaths ?? []), paths.canonicalProject];\n  const defaults = options.defaults ?? {};\n  const user = readConfigPaths(userPaths);\n  const trustedProject = projectIsTrusted(options.isProjectTrusted) ? readConfigPaths(projectPaths) : {};\n  return [\n    defaults,\n    user,\n    options.overlay ?? {},\n    trustedProject,\n    options.environment ?? {},\n    options.cli ?? {},\n    options.programmatic ?? {},\n  ].reduce(mergeConfig, {});\n}\n\nexport async function loadPiConfigAsync(options: PiConfigLoadOptions): Promise<PiConfig> {\n  const paths = piConfigPaths(options.repoRoot, options.homeDirectory);\n  const userPaths = [...paths.legacyUser, ...(options.userConfigPaths ?? []), paths.canonicalUser];\n  const projectPaths = [...paths.legacyProject, ...(options.projectConfigPaths ?? []), paths.canonicalProject];\n  const [user, trustedProject] = await Promise.all([\n    readConfigPathsAsync(userPaths),\n    projectIsTrusted(options.isProjectTrusted) ? readConfigPathsAsync(projectPaths) : Promise.resolve({}),\n  ]);\n  return [\n    options.defaults ?? {},\n    user,\n    options.overlay ?? {},\n    trustedProject,\n    options.environment ?? {},\n    options.cli ?? {},\n    options.programmatic ?? {},\n  ].reduce(mergeConfig, {});\n}\n\n/**\n * Where the image limits are read from and written to.\n *\n * The user layer only. Pi merges a trusted project settings.json over it, but\n * these limits describe one machine's tolerance for image payloads rather than\n * one repository's, and the cockpit page that writes them has no repository\n * scope to offer. The legacy user path is still read, because a machine that\n * has not been migrated still answers from it.\n */\nfunction userSettingsPaths(homeDirectory: string): { legacy: string; canonical: string } {\n  return {\n    legacy: path.join(homeDirectory, CONFIG_DIR_NAME, SETTINGS_FILE_NAME),\n    canonical: path.join(defaultAgentDirectory(homeDirectory), SETTINGS_FILE_NAME),\n  };\n}\n\n/** The file a change lands in; Pi's own settings toggle writes the same one. */\nexport function piImageSettingsPath(homeDirectory = os.homedir()): string {\n  return userSettingsPaths(homeDirectory).canonical;\n}\n\nexport function loadPiImageSettings(homeDirectory = os.homedir()): PiImageSettings {\n  const paths = userSettingsPaths(homeDirectory);\n  return parsePiImageSettings(readConfigPaths([paths.legacy, paths.canonical]));\n}\n\n/**\n * Writes the changed keys back and reports the limits that now apply.\n *\n * Read-modify-write on Pi's own file, so a save races a concurrent Pi settings\n * save rather than merging with it. Pi writes only the fields its UI touched,\n * as this does, which keeps the window to the one setting being changed on\n * both sides at once.\n */\nexport function savePiImageSettings(update: PiImageSettingsUpdate, homeDirectory = os.homedir()): PiImageSettings {\n  const paths = userSettingsPaths(homeDirectory);\n  writePrivateAtomicJson(paths.canonical, applyPiImageSettings(readConfig(paths.canonical), update));\n  return loadPiImageSettings(homeDirectory);\n}\n"],"mappings":"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"}