/** * Coolify API Client * Complete HTTP client for the Coolify API v1 */ import type { CoolifyConfig, DeleteOptions, MessageResponse, MoveResourceResponse, VolumeBackupScheduleRequest, VolumeBackupScheduleResponse, UuidResponse, Server, ServerResource, ServerDomain, Destination, ServerValidation, CreateServerRequest, UpdateServerRequest, Project, CreateProjectRequest, UpdateProjectRequest, Environment, CreateEnvironmentRequest, Application, ApplicationEnvironmentVerification, CreateApplicationPublicRequest, CreateApplicationPrivateGHRequest, CreateApplicationPrivateKeyRequest, CreateApplicationDockerfileRequest, CreateApplicationDockerImageRequest, CreateApplicationDockerComposeRequest, UpdateApplicationRequest, ApplicationActionResponse, EnvironmentVariable, EnvVarSummary, CreateEnvVarRequest, UpdateEnvVarRequest, BulkUpdateEnvVarsRequest, Database, UpdateDatabaseRequest, CreatePostgresqlRequest, CreateMysqlRequest, CreateMariadbRequest, CreateMongodbRequest, CreateRedisRequest, CreateKeydbRequest, CreateClickhouseRequest, CreateDragonflyRequest, CreateDatabaseResponse, DatabaseBackup, BackupExecution, CreateDatabaseBackupRequest, UpdateDatabaseBackupRequest, Service, CreateServiceRequest, UpdateServiceRequest, UpdateServiceApplicationRequest, ServiceCreateResponse, Deployment, DeploymentEssential, DeployTriggerResponse, Team, TeamMember, PrivateKey, CreatePrivateKeyRequest, UpdatePrivateKeyRequest, GitHubApp, CreateGitHubAppRequest, UpdateGitHubAppRequest, GitHubAppUpdateResponse, CloudToken, CreateCloudTokenRequest, UpdateCloudTokenRequest, CloudTokenValidation, Version, StorageListResponse, CreateStorageRequest, UpdateStorageRequest, ScheduledTask, ScheduledTaskExecution, CreateScheduledTaskRequest, UpdateScheduledTaskRequest, HetznerLocation, HetznerServerType, HetznerImage, HetznerSSHKey, CreateHetznerServerRequest, CreateHetznerServerResponse, GitHubRepository, GitHubBranch, ApplicationDiagnostic, ServerDiagnostic, InfrastructureIssuesReport, BatchOperationResult, ResourceListItem, ResourceListItemFull, ServiceSubResource, Tag, AttachTagsRequest } from '../types/coolify.js'; export interface ListOptions { page?: number; per_page?: number; summary?: boolean; } export interface PaginatedResponse { data: T[]; total?: number; page?: number; per_page?: number; } export interface ServerSummary { uuid: string; name: string; ip: string; status?: string; is_reachable?: boolean; } export interface ApplicationSummary { uuid: string; name: string; status?: string; fqdn?: string; git_repository?: string; git_branch?: string; } export interface DatabaseSummary { uuid: string; name: string; type: string; status: string; is_public: boolean; environment_uuid?: string; environment_name?: string; environment_id?: number; } export interface ServiceSummary { uuid: string; name: string; type: string; status: string; domains?: string[]; } export interface DeploymentSummary { uuid: string; deployment_uuid: string; application_name?: string; status: string; created_at: string; } export interface ProjectSummary { uuid: string; name: string; description?: string; } export interface GitHubAppSummary { id: number; uuid: string; name: string; organization: string | null; is_public: boolean; app_id: number | null; } /** * Error thrown for any non-2xx Coolify API response. * * Carries the HTTP status alongside the message so callers can branch on it — * notably the v4.2 GET-to-POST fallback, which must distinguish a 405 (method * rejected by the router, nothing executed) from every other failure. The * message is byte-identical to what a plain `Error` carried before, so this is * a drop-in for anything matching on `error.message`. */ export declare class CoolifyApiError extends Error { readonly status: number; /** Parsed response body, when there was one. Lets callers tell Coolify's routing catch-all apart from a controller's own 404. */ readonly body?: unknown | undefined; constructor(message: string, status: number, /** Parsed response body, when there was one. Lets callers tell Coolify's routing catch-all apart from a controller's own 404. */ body?: unknown | undefined); } /** * Map a failed response's status/path to an actionable hint for known Coolify quirks. * Coolify sometimes returns bodyless errors (e.g. bare `HTTP 500: Internal Server Error`) * that leave the caller guessing at the cause — this appends a short, testable hint for * the cases we've hit in practice. Returns undefined when no known case matches. */ export declare function errorHint(status: number, path: string): string | undefined; /** * Whether an application's status string counts as "up", for the purposes of * deciding what `stopAllApps` targets. * * Coolify reports composite statuses like `running:healthy` and * `exited:unhealthy`, hence substring tests rather than equality. * * Exported because `stopAllApps` uses it to decide what to stop and the #261 * elicitation prompt uses it to tell the human what is about to be stopped. Two * copies of this predicate would eventually disagree, and the failure mode of * that is a confirmation dialog understating its own blast radius. * * **Fixed here, because extraction changed what the bug costs.** `'unhealthy'` * contains `'healthy'`, so the original `includes('healthy')` classified * `exited:unhealthy` as running. Inside `stopAllApps` that cost only a no-op * stop against an already-stopped app, which nobody ever saw. Shared with the * #261 confirmation prompt it does something worse: it names already-dead * applications in a dialog whose entire job is to be accurate, and someone * scanning that list for the one application that should not be in it is handed * noise. A prompt that pads its own blast radius trains people to stop reading * it. * * `running:unhealthy` still counts, via the `running` branch — an application * that is up but failing health checks is still something an emergency stop * should take down. */ export declare function isRunningStatus(status?: string): boolean; /** * HTTP client for the Coolify API */ export declare class CoolifyClient { private readonly baseUrl; private readonly tokens; private readonly customHeaders; private cachedVersion; /** * Endpoints observed to reject POST with a 405, meaning this instance * predates the v4.2 GET-to-POST move and wants the legacy GET. * See {@link postWithLegacyGetFallback}. */ private readonly legacyGetEndpoints; constructor(config: CoolifyConfig); /** * One retry, only on 401, and only when a re-read actually produced a * different token (#398). * * All three conditions matter. Retrying anything but a 401 could double-fire * a state change. Retrying a 401 unconditionally turns a genuinely bad token * into two failed calls per tool instead of one. And re-reading is pointless * unless the token moved, which is why `refresh()` reports whether it did. */ private request; private attempt; /** * Call an endpoint that Coolify v4.2 moved from GET to POST, working against * both eras without any version probing. * * Three endpoints genuinely diverge — `/enable`, `/disable` and * `/servers/{uuid}/validate` are registered `Route::get` only up to v4.1.2 * and `Route::post` only from v4.2 — so neither method works everywhere and * a blanket switch to POST would break every pre-4.2 instance. (The other * endpoints in the v4.2 breaking-change list were already * `Route::match(['get','post'])` in v4.1 and older, so those just send POST * unconditionally.) * * Strategy: try POST, and on a 405 or 404 retry once with GET. The retry is safe * because a 405 comes from the router before the controller runs, so nothing * has executed and there is no risk of double-firing a state change. The same * holds for the catch-all 404, which is identified by its body shape rather * than by status alone so a controller's genuine "not found" stays out of the * retry path. Nothing else triggers the fallback — a 500 in particular * propagates untouched, since it may mean the action partially ran. * * The resolved method is cached per `key`, so the extra round trip is paid at * most once per endpoint rather than per call. `key` is a stable endpoint * identifier rather than the request path, because version compatibility is a * property of the instance, not of the resource — `/servers/{uuid}/validate` * behaves the same for every uuid, so keying on the path would re-probe for * every server. * * The cache self-heals in both directions: if a remembered GET later returns a * 405 (the instance was upgraded to v4.2 while this client was running) the * stale preference is dropped and POST is re-probed, rather than 405ing * forever until restart. */ private postWithLegacyGetFallback; private buildQueryString; getVersion(): Promise; getCachedVersion(): string | null; validateConnection(): Promise; listServers(options?: ListOptions): Promise; getServer(uuid: string): Promise; createServer(data: CreateServerRequest): Promise; updateServer(uuid: string, data: UpdateServerRequest): Promise; deleteServer(uuid: string): Promise; getServerResources(uuid: string): Promise; getServerDomains(uuid: string): Promise; /** * Docker network destinations — the `destination_uuid` that create calls * need on a server with more than one network (#351). Team-wide when * `serverUuid` is omitted. */ listDestinations(serverUuid?: string): Promise; validateServer(uuid: string): Promise; listProjects(options?: ListOptions): Promise; getProject(uuid: string): Promise; createProject(data: CreateProjectRequest): Promise; updateProject(uuid: string, data: UpdateProjectRequest): Promise; deleteProject(uuid: string): Promise; listProjectEnvironments(projectUuid: string): Promise; getProjectEnvironment(projectUuid: string, environmentNameOrUuid: string): Promise; /** * Get environment with missing database types (dragonfly, keydb, clickhouse). * Coolify API omits these from the environment endpoint - we cross-reference * with listDatabases using lightweight summaries. * @see https://github.com/StuMason/coolify-mcp/issues/88 */ getProjectEnvironmentWithDatabases(projectUuid: string, environmentNameOrUuid: string): Promise; createProjectEnvironment(projectUuid: string, data: CreateEnvironmentRequest): Promise; deleteProjectEnvironment(projectUuid: string, environmentNameOrUuid: string): Promise; listApplications(options?: ListOptions): Promise; getApplication(uuid: string, options?: { reveal?: boolean; }): Promise; /** * Verify that one exact application is bound to one exact project environment. * * This intentionally uses only the application detail endpoint and the exact * project/environment endpoint. It never falls back to listing applications, * projects, environments, or resources. The numeric `environment_id` is the * only environment identity Coolify reliably includes on application rows, so * the returned identity is its canonical string representation. */ verifyApplicationEnvironment(applicationUuid: string, projectUuid: string, expectedEnvironment: string): Promise; createApplicationPublic(data: CreateApplicationPublicRequest): Promise; createApplicationPrivateGH(data: CreateApplicationPrivateGHRequest): Promise; createApplicationPrivateKey(data: CreateApplicationPrivateKeyRequest): Promise; createApplicationDockerfile(data: CreateApplicationDockerfileRequest): Promise; createApplicationDockerImage(data: CreateApplicationDockerImageRequest): Promise; /** * @deprecated Coolify removed POST /applications/dockercompose upstream in * v4.1.0 (coollabsio/coolify commit 6ee75cfa) in favour of POST /services. * This 404s against current Coolify releases; use createService instead. * Not exposed via any MCP tool — see #235. */ createApplicationDockerCompose(data: CreateApplicationDockerComposeRequest): Promise; updateApplication(uuid: string, data: UpdateApplicationRequest): Promise; /** * Move a resource to another environment (Coolify v4.2+). * * Each collection is a LITERAL at its own `this.request()` call site rather * than a shared helper taking `collection` as a parameter. The DRY version * reads better and silently weakens the gate: `check:spec-drift` extracts the * template passed to `this.request()` and turns every `${...}` into a wildcard * segment, so `/${collection}/${uuid}/move` collapses to a two-wildcard path * ending in `move` — one route that matches any of the three, and therefore * proves none of them. * * Sent as an unconditional POST. Unlike the enable/disable/validate group, * `/move` has no pre-4.2 GET form, so {@link postWithLegacyGetFallback} would * be wrong here: a retry could only ever hit the same absent route, and on an * instance that did route it a second call would be a second move. An older * instance surfaces the catch-all 404, which {@link errorHint} turns into a * version message. */ moveApplication(uuid: string, environmentUuid: string): Promise; moveDatabase(uuid: string, environmentUuid: string): Promise; moveService(uuid: string, environmentUuid: string): Promise; deleteApplication(uuid: string, options?: DeleteOptions): Promise; getApplicationLogs(uuid: string, lines?: number, showTimestamps?: boolean): Promise; getDatabaseLogs(uuid: string, lines?: number, showTimestamps?: boolean): Promise; /** * Logs for one container inside a service. `subServiceName` is required by * Coolify — a service is a multi-container stack, so "the service logs" is * ambiguous without it. Discover valid names via {@link listServiceApplications} * and {@link listServiceDatabases}. */ getServiceLogs(uuid: string, subServiceName: string, lines?: number, showTimestamps?: boolean): Promise; /** * Every tag on the **current team** — tokens are team-scoped, so this is not * the whole instance. Useful for discovering a name to attach or deploy by. */ listTags(): Promise; listApplicationTags(uuid: string): Promise; listDatabaseTags(uuid: string): Promise; listServiceTags(uuid: string): Promise; attachApplicationTags(uuid: string, data: AttachTagsRequest): Promise; attachDatabaseTags(uuid: string, data: AttachTagsRequest): Promise; attachServiceTags(uuid: string, data: AttachTagsRequest): Promise; detachApplicationTag(uuid: string, tagUuid: string): Promise; detachDatabaseTag(uuid: string, tagUuid: string): Promise; detachServiceTag(uuid: string, tagUuid: string): Promise; listServiceApplications(uuid: string): Promise; listServiceDatabases(uuid: string): Promise; startApplication(uuid: string, options?: { force?: boolean; instant_deploy?: boolean; }): Promise; stopApplication(uuid: string): Promise; restartApplication(uuid: string): Promise; /** * List env vars for an application. * * Default behaviour masks `value` (and `real_value` on the full projection) * with a sentinel string so secrets are not leaked to MCP clients. Pass * `reveal: true` when the caller explicitly needs the plaintext value. */ listApplicationEnvVars(uuid: string, options?: { summary?: boolean; reveal?: boolean; }): Promise; createApplicationEnvVar(uuid: string, data: CreateEnvVarRequest): Promise; updateApplicationEnvVar(uuid: string, data: UpdateEnvVarRequest): Promise; bulkUpdateApplicationEnvVars(uuid: string, data: BulkUpdateEnvVarsRequest): Promise; deleteApplicationEnvVar(uuid: string, envUuid: string): Promise; /** * List databases, augmented from `/resources`. * * Coolify keeps a per-type id sequence and `GET /databases` merges the * per-type collections keyed on that id, so two database types sharing ids * silently shadow each other (verified live: 2 Postgres + 2 Dragonfly, both * pairs ids 1 and 2, returns only the Dragonflys). `/resources` reports every * database correctly, so rows it knows about that `/databases` dropped are * merged back in by uuid. When `/databases` is complete the merge is a no-op; * if `/resources` fails the plain `/databases` result is returned unchanged. * @see https://github.com/StuMason/coolify-mcp/issues/336 */ listDatabases(options?: ListOptions): Promise; getDatabase(uuid: string, options?: { reveal?: boolean; }): Promise; updateDatabase(uuid: string, data: UpdateDatabaseRequest): Promise; deleteDatabase(uuid: string, options?: DeleteOptions): Promise; startDatabase(uuid: string): Promise; stopDatabase(uuid: string): Promise; restartDatabase(uuid: string): Promise; createPostgresql(data: CreatePostgresqlRequest): Promise; createMysql(data: CreateMysqlRequest): Promise; createMariadb(data: CreateMariadbRequest): Promise; createMongodb(data: CreateMongodbRequest): Promise; createRedis(data: CreateRedisRequest): Promise; createKeydb(data: CreateKeydbRequest): Promise; createClickhouse(data: CreateClickhouseRequest): Promise; createDragonfly(data: CreateDragonflyRequest): Promise; listServices(options?: ListOptions): Promise; getService(uuid: string, options?: { reveal?: boolean; }): Promise; createService(data: CreateServiceRequest): Promise; updateService(uuid: string, data: UpdateServiceRequest): Promise; updateServiceApplication(serviceUuid: string, appUuid: string, data: UpdateServiceApplicationRequest, options?: { forceDomainOverride?: boolean; }): Promise; startServiceApplication(serviceUuid: string, appUuid: string, options?: { force?: boolean; latest?: boolean; }): Promise; stopServiceApplication(serviceUuid: string, appUuid: string): Promise; restartServiceApplication(serviceUuid: string, appUuid: string): Promise; deleteService(uuid: string, options?: DeleteOptions): Promise; startService(uuid: string): Promise; stopService(uuid: string): Promise; restartService(uuid: string, pullLatest?: boolean): Promise; /** * List env vars for a service. * * Default behaviour masks `value` (and `real_value`) with a sentinel string * so secrets are not leaked to MCP clients. Pass `reveal: true` when the * caller explicitly needs the plaintext value. */ listServiceEnvVars(uuid: string, options?: { reveal?: boolean; }): Promise; createServiceEnvVar(uuid: string, data: CreateEnvVarRequest): Promise; updateServiceEnvVar(uuid: string, data: UpdateEnvVarRequest): Promise; deleteServiceEnvVar(uuid: string, envUuid: string): Promise; listDeployments(options?: ListOptions): Promise; getDeployment(uuid: string, options?: { includeLogs?: boolean; }): Promise; deployByTagOrUuid(tagOrUuid: string, force?: boolean): Promise; /** * List deployments for an application. * * Coolify returns `{ count, deployments: Deployment[] }` for this endpoint * (NOT a raw array — upstream @masonator type was incorrect). * * By default returns a DeploymentEssential summary (no `logs` field) because * each deployment's log blob can be 30–100KB, and a typical list has 20–35 * deployments — exceeding MCP response token limits. Pass `includeLogs: true` * to also attach the raw log string to each essential projection (never the * raw upstream deployment object, which also embeds the full application/server * graph and secrets). */ listApplicationDeployments(appUuid: string, options?: { includeLogs?: boolean; page?: number; perPage?: number; }): Promise<{ count: number; deployments: DeploymentEssential[]; }>; listTeams(): Promise; getTeam(id: number): Promise; getTeamMembers(id: number): Promise; getCurrentTeam(): Promise; getCurrentTeamMembers(): Promise; listPrivateKeys(): Promise; getPrivateKey(uuid: string): Promise; createPrivateKey(data: CreatePrivateKeyRequest): Promise; updatePrivateKey(uuid: string, data: UpdatePrivateKeyRequest): Promise; deletePrivateKey(uuid: string): Promise; listGitHubApps(options?: ListOptions): Promise; createGitHubApp(data: CreateGitHubAppRequest): Promise; updateGitHubApp(id: number, data: UpdateGitHubAppRequest): Promise; deleteGitHubApp(id: number): Promise; listCloudTokens(): Promise; getCloudToken(uuid: string): Promise; createCloudToken(data: CreateCloudTokenRequest): Promise; updateCloudToken(uuid: string, data: UpdateCloudTokenRequest): Promise; deleteCloudToken(uuid: string): Promise; validateCloudToken(uuid: string): Promise; listDatabaseBackups(databaseUuid: string): Promise; getDatabaseBackup(databaseUuid: string, backupUuid: string): Promise; listBackupExecutions(databaseUuid: string, backupUuid: string): Promise; getBackupExecution(databaseUuid: string, backupUuid: string, executionUuid: string): Promise; createDatabaseBackup(databaseUuid: string, data: CreateDatabaseBackupRequest): Promise; updateDatabaseBackup(databaseUuid: string, backupUuid: string, data: UpdateDatabaseBackupRequest): Promise; deleteDatabaseBackup(databaseUuid: string, backupUuid: string): Promise; listApplicationStorages(uuid: string): Promise; createApplicationStorage(uuid: string, data: CreateStorageRequest): Promise; updateApplicationStorage(uuid: string, data: UpdateStorageRequest): Promise; setApplicationStorageBackup(uuid: string, storageUuid: string, schedule: VolumeBackupScheduleRequest): Promise; setDatabaseStorageBackup(uuid: string, storageUuid: string, schedule: VolumeBackupScheduleRequest): Promise; setServiceStorageBackup(uuid: string, storageUuid: string, schedule: VolumeBackupScheduleRequest): Promise; deleteApplicationStorageBackup(uuid: string, storageUuid: string): Promise; deleteDatabaseStorageBackup(uuid: string, storageUuid: string): Promise; deleteServiceStorageBackup(uuid: string, storageUuid: string): Promise; runApplicationStorageBackup(uuid: string, storageUuid: string): Promise; runDatabaseStorageBackup(uuid: string, storageUuid: string): Promise; runServiceStorageBackup(uuid: string, storageUuid: string): Promise; deleteApplicationStorage(uuid: string, storageUuid: string): Promise; listApplicationScheduledTasks(uuid: string): Promise; createApplicationScheduledTask(uuid: string, data: CreateScheduledTaskRequest): Promise; updateApplicationScheduledTask(uuid: string, taskUuid: string, data: UpdateScheduledTaskRequest): Promise; deleteApplicationScheduledTask(uuid: string, taskUuid: string): Promise; listApplicationScheduledTaskExecutions(uuid: string, taskUuid: string): Promise; deleteApplicationPreview(uuid: string, pullRequestId: number): Promise; /** * List env vars for a database. * * Default behaviour masks `value` (and `real_value`) with a sentinel string * so secrets are not leaked to MCP clients. Pass `reveal: true` when the * caller explicitly needs the plaintext value. Database env vars are among * the most sensitive the server touches (credentials, connection strings), * so this mirrors the masking on {@link listServiceEnvVars}. */ listDatabaseEnvVars(uuid: string, options?: { reveal?: boolean; }): Promise; createDatabaseEnvVar(uuid: string, data: CreateEnvVarRequest): Promise; updateDatabaseEnvVar(uuid: string, data: UpdateEnvVarRequest): Promise; bulkUpdateDatabaseEnvVars(uuid: string, data: BulkUpdateEnvVarsRequest): Promise; deleteDatabaseEnvVar(uuid: string, envUuid: string): Promise; listDatabaseStorages(uuid: string): Promise; createDatabaseStorage(uuid: string, data: CreateStorageRequest): Promise; updateDatabaseStorage(uuid: string, data: UpdateStorageRequest): Promise; deleteDatabaseStorage(uuid: string, storageUuid: string): Promise; deleteBackupExecution(databaseUuid: string, backupUuid: string, executionUuid: string): Promise; bulkUpdateServiceEnvVars(uuid: string, data: BulkUpdateEnvVarsRequest): Promise; listServiceStorages(uuid: string): Promise; createServiceStorage(uuid: string, data: CreateStorageRequest): Promise; updateServiceStorage(uuid: string, data: UpdateStorageRequest): Promise; deleteServiceStorage(uuid: string, storageUuid: string): Promise; listServiceScheduledTasks(uuid: string): Promise; createServiceScheduledTask(uuid: string, data: CreateScheduledTaskRequest): Promise; updateServiceScheduledTask(uuid: string, taskUuid: string, data: UpdateScheduledTaskRequest): Promise; deleteServiceScheduledTask(uuid: string, taskUuid: string): Promise; listServiceScheduledTaskExecutions(uuid: string, taskUuid: string): Promise; listHetznerLocations(tokenUuid: string): Promise; listHetznerServerTypes(tokenUuid: string): Promise; listHetznerImages(tokenUuid: string): Promise; listHetznerSSHKeys(tokenUuid: string): Promise; createHetznerServer(data: CreateHetznerServerRequest): Promise; listGitHubAppRepositories(githubAppId: number): Promise; listGitHubAppBranches(githubAppId: number, owner: string, repo: string): Promise; /** * List every resource on the Coolify instance. * * Defaults to an essential projection ({@link ResourceListItem}: uuid, name, * type, optional status) — Coolify's `/api/v1/resources` endpoint actually * returns ~95 fields per row including the full build/healthcheck/limits * config, which on a moderate instance can exceed 500 KB on a single call * and blow MCP/LLM context budgets. Set `include_full: true` to opt back * into the raw response shape ({@link ResourceListItemFull}). * * When `include_full: true`, sensitive fields ({@link SENSITIVE_RESOURCE_FIELDS}: * webhook HMAC secrets + basic-auth password) are replaced with `'***'` * unless the caller also passes `reveal: true`. Mirrors the v2.9.0 env_vars * masking posture. */ listResources(options?: { include_full?: boolean; reveal?: boolean; }): Promise; getHealth(): Promise; enableApi(): Promise; disableApi(): Promise; cancelDeployment(uuid: string): Promise; /** * Check if a string looks like a UUID (Coolify format or standard format). * Coolify UUIDs are alphanumeric strings, typically 24 chars like "xs0sgs4gog044s4k4c88kgsc" * Also accepts standard UUID format with hyphens like "a1b2c3d4-e5f6-7890-abcd-ef1234567890" */ private isLikelyUuid; /** * Normalize a name/FQDN candidate for exact comparison: lowercase, trimmed, * scheme and trailing slashes stripped. "https://app.example.com/" and * "app.example.com" are the same address to a human asking about it. */ private static normalizeHostLike; /** * Find an application by UUID, name, or domain (FQDN). * An exact name or FQDN match wins outright; substring matching only runs * when nothing matches exactly, so "api.example.com" resolves even when * "api.example.com.staging" also exists (#336). * Returns the UUID if found, throws if not found or multiple matches. */ resolveApplicationUuid(query: string): Promise; /** * Find a server by UUID, name, or IP address. * Returns the UUID if found, throws if not found or multiple matches. */ resolveServerUuid(query: string): Promise; /** * Get comprehensive diagnostic info for an application. * Aggregates: application details, logs, env vars, recent deployments. * @param query - Application UUID, name, or domain (FQDN) */ diagnoseApplication(query: string): Promise; /** * Get comprehensive diagnostic info for a server. * Aggregates: server details, resources, domains, validation. * @param query - Server UUID, name, or IP address */ diagnoseServer(query: string): Promise; /** * Scan infrastructure for common issues. * Critical: unreachable servers, unhealthy apps, exited databases, stopped * services. Warnings (#336): resources running with unknown health, and * servers with an available proxy update (`traefik_outdated_info`, only on * GET /servers/{uuid}, so each listed server is fetched individually). */ findInfrastructureIssues(): Promise; /** * Aggregate results from Promise.allSettled into a BatchOperationResult. */ private aggregateBatchResults; /** * Restart all applications in a project. * @param projectUuid - Project UUID */ /** * Applications belonging to a project. * * **`GET /applications` does not return `project_uuid`.** Verified live * against 4.1.2: none of the 26 applications on the test estate carried the * field, and it is absent from the response entirely. `restartProjectApps` * and `redeployProjectApps` both filtered on it, so both matched zero * applications and silently reported "0 succeeded" instead of doing anything. * * The only link an application carries is the numeric `environment_id`, and * `GET /projects/{uuid}` is what expands a project into its environments. So * the resolution is project → environment ids → applications in those * environments. Verified live: this maps all 26 applications to a project. * * `getProject` rather than the narrower `listProjectEnvironments` * (`GET /projects/{uuid}/environments`) because one call answers both halves * of the question — it returns the environments *and* confirms the project * exists — and it is verified against a live 4.1.2, which the narrower * endpoint is not. `environments` is optional on the `Project` type, so the * check below is what stops that choice degrading into a silent zero; if a * future instance stops expanding it, the fix is to fall back to * `listProjectEnvironments` here rather than to soften the check. * * Deliberately takes no pre-fetched application list. The #261 confirmation * path shares the set the human approved by passing it to the *operation* * (`restartProjectApps` / `redeployProjectApps` both accept it), not by * re-entering this lookup, so a pre-fetch parameter here would have no caller. */ applicationsInProject(projectUuid: string): Promise; /** * Everything a project delete would take with it. * * `DELETE /projects/{uuid}` documents no "project has resources" refusal — * unlike the environment delete, which has an explicit 400 — so the delete is * assumed to cascade, and a confirmation that counts only applications * understates a project holding three Postgres instances and no apps. That is * the direction a destructive prompt must never be wrong in. * * Databases and services resolve exactly like applications: verified live * against 4.1.2, neither list endpoint returns `project_uuid` and both carry * the numeric `environment_id`. * * Returns the `project` too, so the confirmation path does not fetch it a * second time — the one path where an extra round trip happens with a human * waiting on the dialog. */ projectContents(projectUuid: string): Promise<{ project: Project; applications: Application[]; databases: Database[]; services: Service[]; }>; /** * @param projectApps The applications to restart. Pass the set a human already * approved; omit to resolve it from the project. */ restartProjectApps(projectUuid: string, projectApps?: Application[]): Promise; /** * Update or create an environment variable across multiple applications. * Uses upsert behavior: creates if not exists, updates if exists. * @param appUuids - Array of application UUIDs * @param key - Environment variable key * @param value - Environment variable value * @param isBuildtime - Sets the build-time flag on the variable when provided * @param isRuntime - Sets the runtime flag on the variable when provided */ bulkEnvUpdate(appUuids: string[], key: string, value: string, isBuildtime?: boolean, isRuntime?: boolean): Promise; /** * Emergency stop all running applications across entire infrastructure. */ /** * @param runningApps The applications to stop, already filtered to running. * Pass the set a human approved; omit to resolve it from the estate. */ stopAllApps(runningApps?: Application[]): Promise; /** * Redeploy all applications in a project. * @param projectUuid - Project UUID * @param force - Force rebuild (default: true) */ redeployProjectApps(projectUuid: string, force?: boolean, projectApps?: Application[]): Promise; }