"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const adapters_1 = require("../adapters");
const config_1 = require("../config");
const config_2 = require("../config");
const scheduled_1 = require("../adapters/claude/scheduled");
const authz_1 = require("../middleware/authz");
const VALID_TOOLS = ['claude', 'opencode', 'codex', 'qwen', 'gemini', 'terminal'];
function parseTool(req) {
    const raw = req.query.tool;
    if (raw && VALID_TOOLS.includes(raw))
        return raw;
    return 'claude';
}
const router = (0, express_1.Router)();
router.get('/model', (req, res) => {
    const adapter = (0, adapters_1.getAdapter)(parseTool(req));
    const model = adapter.getCurrentModel();
    res.json({ model: model ?? null });
});
/**
 * PUT /model  body: { model: string }
 *
 * Persists the model choice into `~/.claude/settings.json`'s top-level
 * `model` field. In-session switching still happens via sending `/model <x>`
 * to the TUI; this write guarantees that a subsequent `claude` invocation
 * (restart / ccweb wake) picks up the chosen alias as the default.
 *
 * The existing settings.json is merged rather than replaced — hooks,
 * mcpServers, and other user fields are preserved. Only applicable to the
 * `claude` adapter; other tools have their own config files.
 */
router.put('/model', authz_1.requireAdmin, (req, res) => {
    const tool = parseTool(req);
    if (tool !== 'claude') {
        res.status(400).json({ error: 'Persisting model is only implemented for the claude adapter' });
        return;
    }
    const body = (req.body ?? {});
    const raw = typeof body.model === 'string' ? body.model.trim() : '';
    // Whitelist: known aliases, `opus[1m]` / `sonnet[1m]` bracket forms, or a
    // model-like ID `claude-<family>-<ver>`. Refusing arbitrary free-form text
    // prevents injection via JSON-in-settings surprises.
    const OK_RE = /^([a-z]+(\[[a-z0-9-]+\])?|claude-[a-z0-9]+-[a-z0-9-]+)$/i;
    if (!raw || !OK_RE.test(raw) || raw.length > 64) {
        res.status(400).json({ error: 'invalid model alias' });
        return;
    }
    const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
    let settings = {};
    try {
        if (fs.existsSync(settingsPath)) {
            const parsed = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
            if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
                settings = parsed;
            }
            else {
                // Refuse to clobber a non-object settings.json (user error, malformed
                // migration, test fixture, etc.) — better to bail than stringify
                // `[...] + {model:...}` into something Claude Code can't parse.
                res.status(500).json({ error: 'settings.json is not an object — refusing to write `model` field' });
                return;
            }
        }
    }
    catch {
        res.status(500).json({ error: 'Failed to read ~/.claude/settings.json' });
        return;
    }
    settings.model = raw;
    try {
        const dir = path.dirname(settingsPath);
        if (!fs.existsSync(dir))
            fs.mkdirSync(dir, { recursive: true });
        (0, config_2.atomicWriteSync)(settingsPath, JSON.stringify(settings, null, 2));
        res.json({ ok: true, model: raw });
    }
    catch (err) {
        res.status(500).json({ error: `Failed to write settings: ${err.message}` });
    }
});
router.get('/models', (req, res) => {
    const adapter = (0, adapters_1.getAdapter)(parseTool(req));
    res.json(adapter.getAvailableModels());
});
// Skills endpoint. If `projectId` is supplied, we resolve the project's
// folderPath *server-side* after verifying the caller owns (or shares with
// edit) that project — an earlier version accepted a raw `projectPath`
// string from the client, which let any authenticated user enumerate
// `.md` / SKILL.md files under arbitrary paths on the server.
router.get('/skills', (req, res) => {
    const adapter = (0, adapters_1.getAdapter)(parseTool(req));
    const projectId = typeof req.query.projectId === 'string' ? req.query.projectId : undefined;
    let projectPath;
    if (projectId) {
        const project = (0, config_1.getProject)(projectId);
        if (!project) {
            res.status(404).json({ error: 'Project not found' });
            return;
        }
        const username = req.user?.username;
        const shared = project.shares?.some((s) => s.username === username);
        if (!(0, config_1.isProjectOwner)(project, username) && !shared) {
            res.status(403).json({ error: 'Forbidden' });
            return;
        }
        projectPath = project.folderPath;
    }
    const skills = adapter.getSkills(projectPath);
    res.json(skills ?? { builtin: [], custom: [], mcp: [] });
});
/**
 * GET /scheduled?projectId=X
 *
 * Reads `~/.claude/scheduled_tasks.json` and returns the subset of tasks
 * whose originating session's cwd matches this project's folderPath. Only
 * `durable: true` tasks live in that file — session-only /loop jobs are
 * invisible (they exist only in Claude CLI process memory).
 *
 * Claude-only: Codex / Gemini / OpenCode / Qwen have no equivalent.
 */
router.get('/scheduled', (req, res) => {
    const tool = parseTool(req);
    if (tool !== 'claude') {
        res.status(400).json({ error: 'Scheduled tasks are only available for the claude adapter' });
        return;
    }
    const projectId = typeof req.query.projectId === 'string' ? req.query.projectId : undefined;
    if (!projectId) {
        res.status(400).json({ error: 'projectId required' });
        return;
    }
    const project = (0, config_1.getProject)(projectId);
    if (!project) {
        res.status(404).json({ error: 'Project not found' });
        return;
    }
    const username = req.user?.username;
    const shared = project.shares?.some((s) => s.username === username);
    if (!(0, config_1.isProjectOwner)(project, username) && !shared) {
        res.status(403).json({ error: 'Forbidden' });
        return;
    }
    const tasks = (0, scheduled_1.tasksForProject)(project.folderPath);
    res.json({ tasks });
});
exports.default = router;
//# sourceMappingURL=claude.js.map