{
  "openapi": "3.1.0",
  "info": {
    "title": "Ashlr Stack CLI",
    "version": "0.1.0",
    "summary": "Machine-readable catalog of the Ashlr Stack CLI (and MCP tool surface).",
    "description": "This static documentation catalog represents selected local stack CLI commands as GET operations for discovery; it is not a running HTTP API or an exhaustive CLI surface. Source of truth is packages/cli/src/commands/*.ts, mirrored in packages/site/src/lib/cli-ref.generated.ts. The catalog version label does not establish a published or installed package version. Reviewed source is 0.2.1; public CLI and MCP packages recorded at 0.1.1 require Bun 1.2 or newer on PATH even when installed with npm. Launching the MCP server also requires stack on PATH or STACK_BIN; the MCP setup example is in /mcp.json. Inspect actual installed help, advertised tools and returned results. MCP forwards CLI stdout and any nonempty stderr as text; it is not a sandbox or redaction layer. Local MCP resources, when present in the server cwd, return raw file text; emitted provider credential placeholders are literal, not generally expanded by Stack.",
    "contact": {
      "name": "Ashlr",
      "email": "mason@evero-consulting.com",
      "url": "https://stack.ashlr.ai"
    },
    "license": {
      "name": "MIT",
      "identifier": "MIT"
    }
  },
  "servers": [
    {
      "url": "https://stack.ashlr.ai",
      "description": "Documentation and discovery location only; these paths are not HTTP endpoints. The stack CLI runs locally; MCP setup requires both CLI/server commands and the runtime appropriate to the installed versions."
    }
  ],
  "tags": [
    {
      "name": "setup",
      "description": "Scaffolding, provisioning, adoption of existing repos."
    },
    {
      "name": "management",
      "description": "Day-to-day operations on a configured stack."
    },
    {
      "name": "diagnostics",
      "description": "Health, status, and environment inspection."
    },
    {
      "name": "meta",
      "description": "Discovery, upgrades, and cross-project registry."
    },
    {
      "name": "ai",
      "description": "AI-assisted composition: free-text → curated providers (recommend) and recipe replay (apply)."
    }
  ],
  "paths": {
    "/cli/init": {
      "get": {
        "tags": ["setup"],
        "operationId": "stack_init",
        "summary": "Scaffold a .stack.toml in the current directory.",
        "description": "Creates local .stack.toml and .stack.local.toml files; this does not attest Git commit or ignore state. A selected template can also run provider setup. --noInteractive skips the picker but does not override an explicitly selected template. Phantom requirements depend on subsequent operations.",
        "parameters": [
          { "name": "template", "in": "query", "schema": { "type": "string" }, "description": "Starter template slug (e.g. nextjs-supabase-posthog)." },
          { "name": "force", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Overwrite an existing .stack.toml." },
          { "name": "noInteractive", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Skip the template picker; an explicitly supplied template can still be used." }
        ],
        "responses": {
          "200": { "description": "Parsed committed-file reference after scaffolding, not literal CLI JSON stdout or an HTTP response. stack init prints human-facing messages; this catalog retains the JSON media entry only to describe the file representation.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StackToml" } } } }
        }
      }
    },
    "/cli/import": {
      "get": {
        "tags": ["setup"],
        "operationId": "stack_import",
        "summary": "Import an existing .env into Phantom + .stack.toml.",
        "description": "Attempts to store nonempty new variables in Phantom, leaves existing vault names unchanged, and records detected services in local Stack configuration. Inspect skipped names and failures.",
        "parameters": [
          { "name": "from", "in": "query", "schema": { "type": "string", "default": ".env" }, "description": "Path to the .env file to import." },
          { "name": "dryRun", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Print what would happen without writing anything." }
        ],
        "responses": { "200": { "description": "Import summary." } }
      }
    },
    "/cli/scan": {
      "get": {
        "tags": ["setup"],
        "operationId": "stack_scan",
        "summary": "Detect providers used by this repo.",
        "description": "Inspects package.json, lockfiles, config files, and `.env.example` to detect providers already wired into an existing repo.",
        "parameters": [
          { "name": "path", "in": "query", "schema": { "type": "string", "default": "." }, "description": "Directory to scan." },
          { "name": "auto", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "After scanning, offer to run `stack add` for each detected provider interactively." },
          { "name": "confidence", "in": "query", "schema": { "type": "string", "enum": ["low", "medium", "high"], "default": "medium" }, "description": "Minimum confidence to surface." }
        ],
        "responses": { "200": { "description": "Detection report." } }
      }
    },
    "/cli/clone": {
      "get": {
        "tags": ["setup"],
        "operationId": "stack_clone",
        "summary": "Clone a git repo and auto-detect its Stack services.",
        "description": "Clones the repo, then runs `stack scan`. If the repo already ships a committed .stack.toml, prints next-step guidance instead.",
        "parameters": [
          { "name": "url", "in": "query", "required": true, "schema": { "type": "string" }, "description": "GitHub / git URL. Only https://, http://, ssh://, and git@host:org/repo forms are allowed." },
          { "name": "dir", "in": "query", "schema": { "type": "string" }, "description": "Optional target directory (defaults to repo name)." }
        ],
        "responses": { "200": { "description": "Clone + scan report." } }
      }
    },
    "/cli/add": {
      "get": {
        "tags": ["setup"],
        "operationId": "stack_add",
        "summary": "Provision a service and wire its secrets + MCP entry.",
        "description": "Runs the provider's four-step lifecycle: login → provision → materialize → persist. Requires Phantom on PATH.",
        "parameters": [
          { "name": "service", "in": "query", "schema": { "type": "string" }, "description": "Service name (supabase, neon, vercel, …). Omit for interactive picker." },
          { "name": "use", "in": "query", "schema": { "type": "string" }, "description": "Attach to an existing resource by id instead of creating a new one." },
          { "name": "region", "in": "query", "schema": { "type": "string" }, "description": "Region hint for providers that need one (e.g. us-east-1)." },
          { "name": "dryRun", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Preview; no network calls, no vault writes, no MCP edits." }
        ],
        "responses": {
          "200": { "description": "Internal merged ServiceEntry reference from the addService library pipeline, not literal CLI JSON stdout or an HTTP response. stack add prints human-facing messages; the reference does not attest current provider health or credential usability.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ServiceEntry" } } } }
        }
      }
    },
    "/cli/remove": {
      "get": {
        "tags": ["management"],
        "operationId": "stack_remove",
        "summary": "Remove a service from the stack.",
        "description": "Removes local service and MCP configuration and attempts Phantom secret cleanup. Upstream resources are kept in every mode; --keepRemote is a compatibility flag. Headless cleanup or refused terminal confirmation can leave secrets stored. Inspect reported leftovers; command completion does not prove complete teardown.",
        "parameters": [
          { "name": "service", "in": "query", "schema": { "type": "string" }, "description": "Service name. Omit with --all to remove every service in this stack." },
          { "name": "all", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Remove local entries for every configured service; typed confirmation is requested only in a terminal. Phantom secret cleanup can be partial." },
          { "name": "keepRemote", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Accepted compatibility flag; upstream resources are kept regardless of this flag." }
        ],
        "responses": { "200": { "description": "Local removal report; inspect any kept secrets or failures. Upstream resources are not deleted." } }
      }
    },
    "/cli/list": {
      "get": {
        "tags": ["management"],
        "operationId": "stack_list",
        "summary": "List services configured in this stack.",
        "responses": { "200": { "description": "Services array." } }
      }
    },
    "/cli/info": {
      "get": {
        "tags": ["diagnostics"],
        "operationId": "stack_info",
        "summary": "Show everything Stack knows about a configured service.",
        "description": "Prints configured provider/resource/region/auth and MCP metadata plus secret-name inventory; attempts a fresh healthcheck only when the adapter implements one. Metadata and name presence are not credential validity or universal health proof.",
        "parameters": [
          { "name": "service", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Service name." }
        ],
        "responses": { "200": { "description": "Service detail." } }
      }
    },
    "/cli/status": {
      "get": {
        "tags": ["diagnostics"],
        "operationId": "stack_status",
        "summary": "Show stack health at a glance.",
        "responses": { "200": { "description": "Status block." } }
      }
    },
    "/cli/env": {
      "get": {
        "tags": ["diagnostics"],
        "operationId": "stack_env",
        "summary": "Inspect the effective env-var map for this stack.",
        "description": "Has two subcommands: `stack env show` (which secrets are present in Phantom) and `stack env diff` (which declared secrets are missing).",
        "parameters": [
          { "name": "sub", "in": "query", "schema": { "type": "string", "enum": ["show", "diff"], "default": "show" }, "description": "Subcommand." },
          { "name": "env", "in": "query", "schema": { "type": "string", "default": "dev" }, "description": "Environment overlay to preview (show only)." }
        ],
        "responses": { "200": { "description": "Env map." } }
      }
    },
    "/cli/deps": {
      "get": {
        "tags": ["diagnostics"],
        "operationId": "stack_deps",
        "summary": "Show the service dependency graph for this stack.",
        "description": "Renders an ASCII tree grouped by category, with every secret slot annotated beneath its service.",
        "responses": { "200": { "description": "Dependency graph." } }
      }
    },
    "/cli/doctor": {
      "get": {
        "tags": ["diagnostics"],
        "operationId": "stack_doctor",
        "summary": "Inspect supported configured-service healthchecks and their report.",
        "description": "Reports checked, unchecked, warning and error states. --fix attempts setup for eligible failures but can skip stored vault names; inventory presence does not prove credential validity. Setup confirmation depends on the terminal, and headless setup can proceed without that prompt. --all uses recorded local registry projects, not a whole-machine scan. Inspect the report and failures.",
        "parameters": [
          { "name": "fix", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Attempt supported setup remediation for eligible failures; stored vault names can cause skips." },
          { "name": "all", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Run doctor for projects recorded in the local Stack registry." },
          { "name": "json", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Emit machine-readable JSON to stdout." }
        ],
        "responses": { "200": { "description": "Health report." } }
      }
    },
    "/cli/exec": {
      "get": {
        "tags": ["management"],
        "operationId": "stack_exec",
        "summary": "Run a command with Phantom's secret proxy active.",
        "description": "Forwards the command after -- to phantom exec. Secret availability and execution behavior depend on the installed Phantom and project setup; this wrapper does not prove credential injection or grant provider access.",
        "parameters": [
          { "name": "command", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Command to run after the literal `--` separator." }
        ],
        "responses": { "200": { "description": "Spawned child process exit." } }
      }
    },
    "/cli/sync": {
      "get": {
        "tags": ["management"],
        "operationId": "stack_sync",
        "summary": "Push secrets to a deployment platform.",
        "parameters": [
          { "name": "platform", "in": "query", "required": true, "schema": { "type": "string", "enum": ["vercel", "railway", "fly"] }, "description": "Target platform." }
        ],
        "responses": { "200": { "description": "Sync summary." } }
      }
    },
    "/cli/open": {
      "get": {
        "tags": ["meta"],
        "operationId": "stack_open",
        "summary": "Open a service's dashboard in your browser.",
        "parameters": [
          { "name": "service", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Service name." }
        ],
        "responses": { "200": { "description": "Browser spawned." } }
      }
    },
    "/cli/login": {
      "get": {
        "tags": ["management"],
        "operationId": "stack_login",
        "summary": "Refresh OAuth / PAT credentials for a specific provider.",
        "parameters": [
          { "name": "service", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Provider name." }
        ],
        "responses": { "200": { "description": "Auth refreshed." } }
      }
    },
    "/cli/templates": {
      "get": {
        "tags": ["setup"],
        "operationId": "stack_templates",
        "summary": "List or apply starter stack templates.",
        "parameters": [
          { "name": "sub", "in": "query", "schema": { "type": "string", "enum": ["list", "apply"], "default": "list" }, "description": "Subcommand." },
          { "name": "name", "in": "query", "schema": { "type": "string" }, "description": "Template name (apply only)." },
          { "name": "continueOnError", "in": "query", "schema": { "type": "boolean", "default": true }, "description": "Keep going when a single service fails (apply only)." }
        ],
        "responses": { "200": { "description": "Template list or apply summary." } }
      }
    },
    "/cli/providers": {
      "get": {
        "tags": ["meta"],
        "operationId": "stack_providers",
        "summary": "List every curated provider Stack can wire up.",
        "responses": { "200": { "description": "Provider catalog." } }
      }
    },
    "/cli/projects": {
      "get": {
        "tags": ["meta"],
        "operationId": "stack_projects",
        "summary": "Manage the cross-project registry at ~/.stack/projects.json.",
        "parameters": [
          { "name": "sub", "in": "query", "required": true, "schema": { "type": "string", "enum": ["list", "register", "remove", "where"] }, "description": "Subcommand." },
          { "name": "target", "in": "query", "schema": { "type": "string" }, "description": "Project name or absolute path (remove/where)." }
        ],
        "responses": { "200": { "description": "Registry operation result." } }
      }
    },
    "/cli/upgrade": {
      "get": {
        "tags": ["meta"],
        "operationId": "stack_upgrade",
        "summary": "Check npm for a newer @ashlr/stack release.",
        "description": "Does not auto-install — prints an install hint.",
        "responses": { "200": { "description": "Upgrade hint." } }
      }
    },
    "/cli/completion": {
      "get": {
        "tags": ["meta"],
        "operationId": "stack_completion",
        "summary": "Emit shell completion for bash, zsh, or fish.",
        "parameters": [
          { "name": "shell", "in": "query", "required": true, "schema": { "type": "string", "enum": ["bash", "zsh", "fish"] }, "description": "Shell to emit completion for." }
        ],
        "responses": { "200": { "description": "Shell completion script (stdout)." } }
      }
    },
    "/cli/ci": {
      "get": {
        "tags": ["meta"],
        "operationId": "stack_ci",
        "summary": "CI workflow generation (unavailable; use `stack doctor` instead).",
        "parameters": [
          { "name": "sub", "in": "query", "schema": { "type": "string", "enum": ["init"], "default": "init" }, "description": "Subcommand." },
          { "name": "force", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Compatibility flag; generation is unavailable and no workflow is overwritten." }
        ],
        "responses": { "200": { "description": "Command reference only. CI health workflow generation is unavailable; ci init refuses without writing a workflow." } }
      }
    },
    "/cli/recommend": {
      "get": {
        "tags": ["ai"],
        "operationId": "stack_recommend",
        "summary": "Pick the right providers for a free-text project description.",
        "description": "Returns scored curated-provider hits and per-category ranking for a project description. The default selection is pure BM25 retrieval without LLM inference. --save writes a Recipe; --synth attempts optional model-authored rationales. --json prints the CLI RecommendOutput payload, which the MCP server forwards as text rather than a structured MCP result. Inspect completion and output; this is not a latency or zero-I/O guarantee.",
        "parameters": [
          { "name": "query", "in": "query", "schema": { "type": "string" }, "description": "Free-text description (positional in the CLI; omit for usage hint)." },
          { "name": "k", "in": "query", "schema": { "type": "integer", "default": 6 }, "description": "Max top-level hits to return." },
          { "name": "category", "in": "query", "schema": { "type": "string", "enum": [
  "Database",
  "Deploy",
  "Cloud",
  "AI",
  "Analytics",
  "Observability",
  "Errors",
  "Payments",
  "Code",
  "Tickets",
  "Email",
  "Auth",
  "FeatureFlags"
] }, "description": "Restrict to a single category." },
          { "name": "json", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Machine-readable JSON output." },
          { "name": "save", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Freeze the result to .stack/recipes/<id>.toml for later `stack apply`." },
          { "name": "synth", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Call a local SLM to synthesize rationales. Falls back to retrieval-only if no endpoint is reachable." }
        ],
        "responses": {
          "200": {
            "description": "Recommendation payload.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RecommendOutput" } } }
          }
        }
      }
    },
    "/cli/apply": {
      "get": {
        "tags": ["management"],
        "operationId": "stack_apply",
        "summary": "Apply a saved recipe through provider setup, a Phantom vault check and webhook stubs.",
        "description": "Runs the saved Recipe through provider setup, then reports which declared secrets are in the Phantom vault and which are missing (names only; it never writes to the vault) and writes webhook stubs. Inspect failures and skips; complete setup is not guaranteed. --noWire skips the vault check and stubs, not provider credential writes. Requires local Stack configuration; partial failures can require manual cleanup.",
        "parameters": [
          { "name": "recipeId", "in": "query", "schema": { "type": "string" }, "description": "Recipe id (filename stem in .stack/recipes/). Omit for an interactive picker." },
          { "name": "noWire", "in": "query", "schema": { "type": "boolean", "default": false }, "description": "Skip the Phantom vault check and webhook stubs; provider credential writes remain." }
        ],
        "responses": { "200": { "description": "Apply summary (each provider's add result + phantom-wire counts)." } }
      }
    }
  },
  "components": {
    "schemas": {
      "StackToml": {
        "type": "object",
        "title": ".stack.toml (canonical committed shape)",
        "description": "JSON-like representation of the parsed committed .stack.toml produced by writeConfig, not raw TOML text, the merged readConfig result, or every input the permissive reader accepts. Per-clone instance data lives in .stack.local.toml. This reference does not add runtime schema validation.",
        "required": ["stack", "services", "environments"],
        "properties": {
          "stack": {
            "type": "object",
            "required": ["version"],
            "properties": {
              "version": { "type": "string", "example": "1", "description": "emptyConfig starts at version 1; writeConfig preserves the supplied string. The reader does not enforce a version enum or migration promise." },
              "template": { "type": "string", "description": "Optional template slug." }
            }
          },
          "services": {
            "type": "object",
            "description": "Map from logical service slot name to its committed provider, secret names and optional MCP entry name. Instance fields are not written here.",
            "additionalProperties": {
              "type": "object",
              "required": ["provider", "secrets"],
              "properties": {
                "provider": { "type": "string", "description": "Provider key; readConfig does not validate it against the provider registry." },
                "secrets": { "type": "array", "items": { "type": "string" }, "description": "Secret slot names, not the corresponding credential values. An empty array is allowed." },
                "mcp": { "type": "string", "description": "Optional MCP server entry name; its presence alone does not attest a running server." }
              }
            }
          },
          "environments": {
            "type": "array",
            "description": "Environment entries preserved by writeConfig, including an empty array. This schema does not enforce exactly one default or promise expansion of overrides.",
            "items": {
              "type": "object",
              "required": ["name"],
              "properties": {
                "name": { "type": "string" },
                "default": { "type": "boolean" },
                "overrides": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Optional per-environment string map." }
              }
            }
          }
        }
      },
      "ServiceEntry": {
        "type": "object",
        "title": "Merged internal service entry",
        "description": "Internal ServiceEntry returned by readConfig and the addService library pipeline, combining committed shape with per-clone instance fields. It is not the committed service subset or a literal stack add JSON response. Freeform metadata is not a redaction or secret-storage boundary.",
        "required": ["provider", "secrets", "created_at"],
        "properties": {
          "provider": { "type": "string", "description": "Provider key; the service map key is the logical slot name." },
          "secrets": { "type": "array", "items": { "type": "string" }, "description": "Secret slot names associated with this service." },
          "created_at": { "type": "string", "description": "Instance timestamp string; this reference does not add date-format validation." },
          "mcp": { "type": "string", "description": "Optional MCP server entry name." },
          "resource_id": { "type": "string", "description": "Opaque provider-side instance identifier." },
          "region": { "type": "string", "description": "Optional instance region." },
          "meta": { "type": "object", "description": "Optional freeform provider-specific instance metadata; values are not generally redacted or moved into Phantom." },
          "created_by": { "type": "string", "description": "Optional instance creator label." }
        }
      },
      "RecommendOutput": {
        "type": "object",
        "title": "RecommendOutput",
        "description": "CLI JSON payload printed by stack recommend --json; the MCP server forwards that output as text. This schema is documentation, not a structured MCP result or installed-version attestation.",
        "required": ["query", "hits", "byCategory", "guidance"],
        "properties": {
          "query": { "type": "string", "description": "The exact free-text query that was scored." },
          "hits": {
            "type": "array",
            "description": "Top-k scored providers (ranked across all categories).",
            "items": {
              "type": "object",
              "required": ["name", "displayName", "category", "authKind", "secrets", "blurb", "score", "matched"],
              "properties": {
                "name": { "type": "string", "description": "CLI-facing name (what you type after `stack add`)." },
                "displayName": { "type": "string" },
                "category": { "type": "string", "enum": ["Database", "Deploy", "Cloud", "AI", "Analytics", "Errors", "Payments", "Code", "Tickets", "Email", "Auth"] },
                "authKind": { "type": "string", "enum": ["oauth_pkce", "oauth_device", "pat", "api_key"] },
                "secrets": { "type": "array", "items": { "type": "string" } },
                "blurb": { "type": "string" },
                "score": { "type": "number", "description": "BM25 score, rounded to 3 decimals." },
                "matched": { "type": "array", "items": { "type": "string" }, "description": "Terms from the query that matched this provider." },
                "rationale": { "type": "string", "description": "Optional synth-authored rationale (present only with --synth)." }
              }
            }
          },
          "byCategory": {
            "type": "object",
            "description": "Per-category ranking (top candidates per category, for agents that want a structured plan).",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "object",
                "required": ["name", "score"],
                "properties": {
                  "name": { "type": "string" },
                  "score": { "type": "number" }
                }
              }
            }
          },
          "guidance": { "type": "string", "description": "One-paragraph next-step guidance for the user / agent." },
          "recipe": {
            "type": "object",
            "description": "Present only when `--save` is used.",
            "required": ["id", "path"],
            "properties": {
              "id": { "type": "string", "description": "Recipe id (filename stem in .stack/recipes/)." },
              "path": { "type": "string", "description": "Absolute path to the written recipe TOML." }
            }
          },
          "inference": {
            "type": "object",
            "description": "Present only when `--synth` succeeded.",
            "required": ["mode", "backend"],
            "properties": {
              "mode": { "type": "string", "enum": ["synth", "mcp-delegated"] },
              "backend": { "type": "string", "description": "Backend name (e.g. lmstudio, ollama)." },
              "note": { "type": "string" }
            }
          }
        }
      },
      "ProviderEntry": {
        "type": "object",
        "title": "Provider",
        "description": "One curated provider Stack knows how to wire.",
        "required": ["name", "category", "authKind", "secrets"],
        "properties": {
          "name": { "type": "string", "description": "CLI-facing name (what you type after `stack add`)." },
          "displayName": { "type": "string" },
          "category": {
            "type": "string",
            "enum": ["Database", "Deploy", "Cloud", "AI", "Analytics", "Errors", "Payments", "Code", "Tickets", "Email", "Auth"]
          },
          "authKind": {
            "type": "string",
            "enum": ["oauth_pkce", "oauth_device", "pat", "api_key"],
            "description": "Auth flow type."
          },
          "secrets": { "type": "array", "items": { "type": "string" } },
          "mcp": {
            "type": "object",
            "properties": {
              "name": { "type": "string" },
              "detail": { "type": "string" },
              "preview": { "type": "boolean" }
            }
          },
          "dashboard": { "type": "string", "format": "uri" },
          "docs": { "type": "string", "format": "uri" }
        }
      }
    }
  }
}
