48 lines
1.6 KiB
TypeScript
48 lines
1.6 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import { existsSync, statSync } from 'node:fs'
|
|
import { resolve } from 'node:path'
|
|
import { requireRole } from '@/lib/auth'
|
|
import { runCommand } from '@/lib/command'
|
|
|
|
function isAllowedDirectory(input: string): boolean {
|
|
const cwd = resolve(input)
|
|
if (!cwd.startsWith('/')) return false
|
|
if (!(cwd.startsWith('/Users/') || cwd.startsWith('/tmp/') || cwd.startsWith('/var/folders/'))) {
|
|
return false
|
|
}
|
|
if (!existsSync(cwd)) return false
|
|
try {
|
|
return statSync(cwd).isDirectory()
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/local/terminal
|
|
* Body: { cwd: string }
|
|
* Opens a new local Terminal window at the given working directory.
|
|
*/
|
|
export async function POST(request: NextRequest) {
|
|
const auth = requireRole(request, 'operator')
|
|
if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
|
|
|
|
const body = await request.json().catch(() => ({}))
|
|
const cwd = typeof body?.cwd === 'string' ? body.cwd.trim() : ''
|
|
if (!cwd) {
|
|
return NextResponse.json({ error: 'cwd is required' }, { status: 400 })
|
|
}
|
|
if (!isAllowedDirectory(cwd)) {
|
|
return NextResponse.json({ error: 'cwd must be an existing safe local directory' }, { status: 400 })
|
|
}
|
|
|
|
try {
|
|
await runCommand('open', ['-a', 'Terminal', cwd], { timeoutMs: 10_000 })
|
|
return NextResponse.json({ ok: true, message: `Opened Terminal at ${cwd}` })
|
|
} catch (error: any) {
|
|
return NextResponse.json({ error: error?.message || 'Failed to open Terminal' }, { status: 500 })
|
|
}
|
|
}
|
|
|
|
export const dynamic = 'force-dynamic'
|