← back to ClawCoder

src/app/api/files/route.ts

99 lines

import { NextRequest, NextResponse } from 'next/server'
import fs from 'fs'
import path from 'path'
import { authenticateRequest } from '@/lib/auth'

export interface ClaudeMdEntry {
  path: string
  project: string
}

export const CLAUDE_MD_PATHS: ClaudeMdEntry[] = [
  { path: '/root/.claude/CLAUDE.md', project: 'Global Configuration' },
  { path: '/root/CLAUDE.md', project: 'Project Router' },
  { path: '/root/DW-Agents/CLAUDE.md', project: 'DW-Agents' },
  { path: '/root/Projects/Designer-Wallcoverings/CLAUDE.md', project: 'Designer Wallcoverings' },
  { path: '/root/Projects/dw-war-room/CLAUDE.md', project: '3D War Room' },
  { path: '/root/Projects/watches/CLAUDE.md', project: 'Omega Watches' },
  { path: '/root/Projects/dear-bubbe-nextjs/CLAUDE.md', project: 'Bubbe AI' },
  { path: '/root/Projects/wine-finder/CLAUDE.md', project: 'Wine Finder' },
  { path: '/root/Projects/Letsbegin/CLAUDE.md', project: 'Letsbegin / Ralph' },
  { path: '/root/Projects/Norma/CLAUDE.md', project: 'Norma Intelligence' },
  { path: '/root/Projects/jill-website/CLAUDE.md', project: 'Jill Website (Nosara)' },
  { path: '/root/Projects/Sascha/CLAUDE.md', project: 'Sascha Portfolio' },
  { path: '/root/Projects/SmokeShop/CLAUDE.md', project: 'SmokeShop' },
  { path: '/root/Projects/Grant/CLAUDE.md', project: 'Grant Fundraiser' },
  { path: '/root/Projects/Freddy/CLAUDE.md', project: 'Freddy Fundraiser' },
  { path: '/root/Projects/Patty/CLAUDE.md', project: 'Patty Petitions' },
  { path: '/root/Projects/angels-flowers/CLAUDE.md', project: 'Angels Flowers' },
  { path: '/root/Projects/wine-finder-next/CLAUDE.md', project: 'Wine Finder Next' },
  { path: '/root/Projects/B_Version_1/.claude/CLAUDE.md', project: 'Dear Bubbe v1' },
  { path: '/root/Projects/Letsbegin/.claude/CLAUDE.md', project: 'GoodQuestion Ralph' },
  { path: '/root/Projects/Designer-Wallcoverings/DW-Agents/CLAUDE.md', project: 'DW-Agents (sub)' },
  { path: '/root/Projects/Designer-Wallcoverings/DW-Websites/CLAUDE.md', project: 'DW Websites' },
  { path: '/root/Projects/Designer-Wallcoverings/DW-Programming/CLAUDE.md', project: 'DW Programming' },
  { path: '/root/Projects/Designer-Wallcoverings/DW-Programming/shopifyskutoblog/CLAUDE.md', project: 'DW SKU to Blog' },
  { path: '/root/Projects/Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL/CLAUDE.md', project: 'DW URL Import' },
  { path: '/root/Projects/Designer-Wallcoverings/DW-MCP/CLAUDE.md', project: 'DW MCP' },
  { path: '/root/Projects/Designer-Wallcoverings/DW-Agents/dw-agents/CLAUDE.md', project: 'DW-Agents Core' },
  { path: '/root/WebsitesMisc/WineFinder/CLAUDE.md', project: 'Wine Finder Misc' },
  { path: '/root/Projects/ClawCoder/docs/superpowers/specs/2026-03-23-clawcoder-profiles-analyzer-design.md', project: 'ClawCoder Spec' },
]

/** Set of allowed paths for security validation */
export const ALLOWED_PATHS = new Set(CLAUDE_MD_PATHS.map(e => e.path))

export interface ClaudeMdFile {
  id: string
  path: string
  project: string
  lines: number
  hasRules: boolean
  lastModified: string
  preview: string
  exists: boolean
}

function getFileInfo(entry: ClaudeMdEntry): ClaudeMdFile {
  const id = Buffer.from(entry.path).toString('base64')
  const result: ClaudeMdFile = {
    id,
    path: entry.path,
    project: entry.project,
    lines: 0,
    hasRules: false,
    lastModified: '',
    preview: '',
    exists: false,
  }

  try {
    const content = fs.readFileSync(entry.path, 'utf-8')
    const stat = fs.statSync(entry.path)
    result.exists = true
    result.lines = content.split('\n').length
    result.lastModified = stat.mtime.toISOString()
    result.preview = content.substring(0, 100).replace(/\n/g, ' ')

    // Check for .claude/rules/ near the file
    const dir = path.dirname(entry.path)
    const rulesDir = entry.path.includes('.claude/CLAUDE.md')
      ? path.join(dir, 'rules')
      : path.join(dir, '.claude', 'rules')
    result.hasRules = fs.existsSync(rulesDir) && fs.statSync(rulesDir).isDirectory()
  } catch {
    // File doesn't exist or can't be read
    result.exists = false
  }

  return result
}

export async function GET(request: NextRequest) {
  const auth = authenticateRequest(request)
  if (auth instanceof NextResponse) return auth

  const files = CLAUDE_MD_PATHS.map(getFileInfo)
  return NextResponse.json(files)
}