pitchfork-cli 2.27.0

Daemons with DX
Documentation
use crate::Result;
use serde::Serialize;

/// Generate JSON documentation for the web API endpoints
#[derive(Debug, usage_rs::Args)]
pub struct ApiSchema;

#[derive(Serialize)]
struct ApiDoc {
    endpoints: Vec<Endpoint>,
}

#[derive(Serialize)]
struct Endpoint {
    path: &'static str,
    method: &'static str,
    description: &'static str,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    path_params: Vec<Param>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    query_params: Vec<Param>,
    #[serde(skip_serializing_if = "Option::is_none")]
    request_body: Option<&'static str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    response_type: Option<&'static str>,
    auth: bool,
}

#[derive(Serialize)]
struct Param {
    name: &'static str,
    type_name: &'static str,
    description: &'static str,
    required: bool,
}

impl ApiSchema {
    pub async fn run(&self) -> Result<()> {
        let doc = ApiDoc {
            endpoints: vec![
                Endpoint {
                    path: "/api/stats",
                    method: "GET",
                    description: "Return system-level statistics: process count, CPU count, and memory usage.",
                    path_params: vec![],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some("ApiStats"),
                    auth: true,
                },
                Endpoint {
                    path: "/api/daemons",
                    method: "GET",
                    description: "List all daemons with full state including status, ports, CPU/memory usage, and proxy URLs.",
                    path_params: vec![],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some("Vec<ApiDaemonEntry>"),
                    auth: true,
                },
                Endpoint {
                    path: "/api/daemons/{id}",
                    method: "GET",
                    description: "Get a single daemon by its qualified ID.",
                    path_params: vec![Param {
                        name: "id",
                        type_name: "String",
                        description: "Qualified daemon ID (e.g. \"project/hello\")",
                        required: true,
                    }],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some("ApiDaemonEntry"),
                    auth: true,
                },
                Endpoint {
                    path: "/api/daemons/{id}/start",
                    method: "POST",
                    description: "Start a stopped or failed daemon.",
                    path_params: vec![Param {
                        name: "id",
                        type_name: "String",
                        description: "Qualified daemon ID",
                        required: true,
                    }],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some(r#"{ "ok": bool, "error": String|null }"#),
                    auth: true,
                },
                Endpoint {
                    path: "/api/daemons/{id}/stop",
                    method: "POST",
                    description: "Gracefully stop a running daemon.",
                    path_params: vec![Param {
                        name: "id",
                        type_name: "String",
                        description: "Qualified daemon ID",
                        required: true,
                    }],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some(r#"{ "ok": bool, "error": null }"#),
                    auth: true,
                },
                Endpoint {
                    path: "/api/daemons/{id}/restart",
                    method: "POST",
                    description: "Stop and then start a daemon.",
                    path_params: vec![Param {
                        name: "id",
                        type_name: "String",
                        description: "Qualified daemon ID",
                        required: true,
                    }],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some(r#"{ "ok": bool, "error": String|null }"#),
                    auth: true,
                },
                Endpoint {
                    path: "/api/daemons/{id}/enable",
                    method: "POST",
                    description: "Enable a daemon so it can be started.",
                    path_params: vec![Param {
                        name: "id",
                        type_name: "String",
                        description: "Qualified daemon ID",
                        required: true,
                    }],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some(r#"{ "ok": bool, "error": String|null }"#),
                    auth: true,
                },
                Endpoint {
                    path: "/api/daemons/{id}/disable",
                    method: "POST",
                    description: "Disable a daemon so it cannot be started.",
                    path_params: vec![Param {
                        name: "id",
                        type_name: "String",
                        description: "Qualified daemon ID",
                        required: true,
                    }],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some(r#"{ "ok": bool, "error": String|null }"#),
                    auth: true,
                },
                Endpoint {
                    path: "/api/logs/{id}/tail",
                    method: "GET",
                    description: "Stream daemon logs via SSE (Server-Sent Events). Each line is prefixed with \"data: \".",
                    path_params: vec![Param {
                        name: "id",
                        type_name: "String",
                        description: "Qualified daemon ID",
                        required: true,
                    }],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some("text/event-stream"),
                    auth: true,
                },
                Endpoint {
                    path: "/api/namespaces",
                    method: "GET",
                    description: "List all registered namespaces.",
                    path_params: vec![],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some("Vec<ApiNamespaceEntry>"),
                    auth: true,
                },
                Endpoint {
                    path: "/api/namespaces",
                    method: "POST",
                    description: "Register a new namespace by directory path.",
                    path_params: vec![],
                    query_params: vec![],
                    request_body: Some(r#"{ "dir": "String" }"#),
                    response_type: Some(r#"{ "ok": bool }"#),
                    auth: true,
                },
                Endpoint {
                    path: "/api/namespaces/{name}",
                    method: "DELETE",
                    description: "Remove a namespace by name.",
                    path_params: vec![Param {
                        name: "name",
                        type_name: "String",
                        description: "Namespace name",
                        required: true,
                    }],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some(r#"{ "ok": bool }"#),
                    auth: true,
                },
                Endpoint {
                    path: "/api/proxies",
                    method: "GET",
                    description: "List all configured proxy slugs with their target daemon, worktree, and URL.",
                    path_params: vec![],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some("Vec<ApiProxyWorktreeEntry>"),
                    auth: true,
                },
                Endpoint {
                    path: "/api/projects",
                    method: "GET",
                    description: "List registered projects with worktree counts, daemon counts, and last activity.",
                    path_params: vec![],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some("Vec<ApiProjectSummary>"),
                    auth: true,
                },
                Endpoint {
                    path: "/api/projects/{project}",
                    method: "GET",
                    description: "Show one project: every known worktree, including never-started ones, plus the primary checkout's stack.",
                    path_params: vec![Param {
                        name: "project",
                        type_name: "string",
                        description: "Registered project name (matched case-insensitively)",
                        required: true,
                    }],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some("ApiProject"),
                    auth: true,
                },
                Endpoint {
                    path: "/api/projects/{project}/{worktree}",
                    method: "GET",
                    description: "Show one worktree's stack: the daemon groups declared for it and each member daemon's state. Read-only; it never starts a daemon.",
                    path_params: vec![
                        Param {
                            name: "project",
                            type_name: "string",
                            description: "Registered project name (matched case-insensitively)",
                            required: true,
                        },
                        Param {
                            name: "worktree",
                            type_name: "string",
                            description: "Worktree URL name or branch name",
                            required: true,
                        },
                    ],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some("ApiStack"),
                    auth: true,
                },
                Endpoint {
                    path: "/api/processes/{id}/tree",
                    method: "GET",
                    description: "Return the process tree rooted at the given daemon's PID, including child processes.",
                    path_params: vec![Param {
                        name: "id",
                        type_name: "String",
                        description: "Qualified daemon ID",
                        required: true,
                    }],
                    query_params: vec![],
                    request_body: None,
                    response_type: Some("Vec<ApiProcessTree>"),
                    auth: true,
                },
            ],
        };

        let json =
            serde_json::to_string_pretty(&doc).expect("failed to serialize API schema to JSON");
        println!("{json}");
        Ok(())
    }
}