Generated banner for Launch With AWS
Generated logo for Launch With AWS

Launch With AWS

AWSOfficialPublished by AWS

  • Skill
  • Workflow
  • Cloud and DevOps
  • Apache-2.0
  • Medium risk
  • Has scripts

From the author

Migrates vibe-coded web applications to AWS. Handles the full workflow from analysis through migration to deployment, producing deployable AWS Blocks infrastructure code. Supports full-stack apps built with vibe-coding platforms (Lovable, Bolt.new, Replit) and frontend web applications and websites: React, Vue, Angular, Next.js, Nuxt, Astro, SvelteKit, Gatsby, Vite, Svelte, Solid, Docusaurus, and others (static sites, SPAs, and SSR frameworks with static export). Triggers on: launch with AWS, launch on AWS, deploy to AWS, migrate to AWS, host my app on AWS, move my app to AWS, transfer my app to AWS. Activates when the user wants to migrate a vibe-coded app or frontend web app to AWS, even if they don't say 'migrate' explicitly.
— the description by AWS

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

Launch with AWS provides a structured, automated workflow to migrate full-stack web applications and frontend projects to AWS. It analyzes the source code, generates a migration plan with cost estimates, and produces deployable AWS Blocks infrastructure code. The process includes secure authentication, source code analysis, and a guided execution phase that results in a migration snapshot ready for local application and deployment via generated instructions.

What it does

  • Analyzes web application source code to determine migration requirements.
  • Generates detailed migration plans and AWS cost estimates.
  • Produces AWS Blocks infrastructure code for target applications.
  • Handles secure authentication and session management for AWS migration services.
  • Provides a guided 3-way merge process to integrate migrated code into existing repositories.
  • Generates project-specific deployment instructions via DEPLOY.md.

When to use it

  • When you need to move a vibe-coded or frontend web application to AWS.
  • When you want to transition from platforms like Lovable, Bolt.new, or Replit to AWS-native infrastructure.
  • When you require a structured, step-by-step migration path for static sites, SPAs, or SSR frameworks.
  • When you need to estimate the AWS infrastructure costs for hosting your existing web application.

Advantages

  • Automates the complex process of infrastructure definition and migration planning.
  • Provides clear cost visibility before committing to a migration.
  • Maintains security by using encrypted data handling and avoiding AI training on user code.
  • Supports a wide range of modern web frameworks and platforms.
  • Ensures a clean integration process through git-based migration branches.

Usage examples

“Migrate my Replit web app to AWS.”
The agent initiates the authentication flow, analyzes the Replit project structure, and prepares a migration plan for AWS Lambda and Aurora Serverless.
“How much would it cost to host my Next.js site on AWS?”
The agent analyzes the project, calculates the estimated monthly AWS costs, and presents a breakdown of the required services.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/aws/launch-with-aws/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip launch-with-aws.zip -d .agents/skills

Documentation by AWS

Launch with AWS

Drives an AWS migration end-to-end using CLI scripts. Takes a user's web application, analyzes it, generates a migration plan with cost estimate, and delivers deployable AWS Blocks infrastructure code.

The AWS MCP server is recommended but is not required. This skill works standalone via its CLI scripts in any agent environment.

Script Invocation

All commands are run via:

python3 scripts/launch_with_aws.py <command> [args...]

where scripts/ is relative to this skill directory. The agent MUST set the working directory to the skill root before invoking commands.

Required files: launch_with_aws.py, launch_config.py, auth.py, auth_callback_server.py, launch_api_client.py, archive.py, service model. When loaded via MCP, fetch all and write to a temp directory preserving structure before invoking.

Each command outputs JSON to stdout on success, or exits non-zero with a JSON error on stderr.

Two global flags may be added to any command: --region <region> selects the region the service runs the migration in, and --aws-mcp-url <url> passes the URL of the AWS MCP server the agent is connected to. See step 2.

Dependencies: Python 3.10+ and boto3. The script checks both on startup and exits with a clear error if either is missing.

Supported Application Types

Full-stack apps built with vibe-coding platforms, and frontend web applications and websites (static sites, SPAs, and SSR frameworks with static export).

Origin PlatformWhat it covers
LovableLovable-generated full-stack apps (React + Supabase)
Bolt.newBolt.new-generated full-stack apps (React + Supabase)
ReplitReplit-hosted full-stack apps (React + Express.js + PostgreSQL)
FrameworkExamples
React ecosystemReact, CRA, Vite + React, Gatsby, Docusaurus
Vue ecosystemVue, Nuxt (static export), VitePress
AngularAngular
Svelte ecosystemSvelte, SvelteKit (static export)
SSR with static exportNext.js, Nuxt, Astro, SvelteKit
Other modern frameworksAstro, Solid, Preact, Lit, Eleventy
Vite (generic)Any Vite-based app

Other frameworks may also work. If the user's app doesn't match these, see Unsupported Application Handling below.

What Gets Migrated vs. What Stays

Lovable / Bolt.new apps (Supabase-backed):

ComponentWhat happens
Frontend & hostingMigrated to AWS (S3 + CloudFront + Lambda)
Edge functions / server functionsMigrated to AWS Lambda
AI calls (e.g. Lovable AI Gateway)Migrated to Amazon Bedrock
Database (Supabase DB)Stays on Supabase — not migrated
Auth (Supabase Auth)Stays on Supabase — not migrated
Storage & RealtimeStays on Supabase — not migrated

The app continues to call Supabase for database, auth, storage, and realtime from the AWS-hosted application.

Replit apps (Express.js + PostgreSQL):

ComponentWhat happens
Frontend & hostingMigrated to AWS (S3 + CloudFront + Lambda)
Server logic (Express.js)Migrated to AWS Lambda (API Gateway)
Database (PostgreSQL)Schema and code migrated to AWS (Aurora Serverless / DynamoDB). Existing data is NOT migrated — customers must export and import their data separately.
Auth (Replit Auth)Code migrated to AWS (Cognito). Existing user accounts are NOT migrated — customers must re-create or invite users in Cognito.
Realtime (WebSockets)Migrated to AWS (API Gateway WebSocket)
File storageMigrated to AWS (S3). Existing files are NOT migrated.

Replit app infrastructure and code are migrated to AWS-native services, but existing data, user accounts, and files must be migrated separately by the customer.

Input Resolution

Resolve the user's input to a local directory path:

  • If the user provides a local path: pass that path directly.

  • If the user provides a GitHub URL: clone the repository locally and use that directory.

  • If neither is provided: use the current working directory. If it doesn't look like an app directory, ask the user for the path.

Flow

Run the script commands in order, surfacing results to the user at each step:

1. Authentication
python3 scripts/launch_with_aws.py auth-start

Always run first. Returns immediately with JSON:

  • If already authenticated: {"authenticated": true, "reusedCachedSession": true, "baseUrl": "..."}

  • If silent refresh succeeded: {"authenticated": true, "reusedCachedSession": false, "baseUrl": "..."}

  • If interactive sign-in is needed: {"authenticated": false, "signInUrl": "https://...", "pid": 12345, "port": 54321, "baseUrl": "..."}

When authenticated is false, immediately display the signInUrl to the user (so they can open it in their browser) and call auth-wait in the same response:

python3 scripts/launch_with_aws.py auth-wait <pid>

where <pid> is the pid value from the auth-start response. This blocks until the user completes browser sign-in (or times out after 600s). Returns {"authenticated": true, "baseUrl": "..."} on success.

Sessions are capped at 90 days even if the identity provider does not set an expiration; after that the interactive flow is required again.

To check the current session without authenticating, or to sign out:

python3 scripts/launch_with_aws.py session-status
python3 scripts/launch_with_aws.py sign-out

session-status reports whether a session exists and how long until the token and overall session expire. sign-out best-effort revokes the refresh token and deletes the local ~/.launch-with-aws/session.json. On shared or untrusted workstations, run sign-out when finished.

2. Create Launch

For a local directory, present this data-handling confirmation verbatim and wait for explicit approval:

Your source code will be uploaded to the Launch with AWS service in [regionName], [region] ([sourceLabel]) to analyze your application and generate a migration plan. If you later approve execution, an AWS-hosted agent will modify a copy of your source code according to the plan and produce a migrated snapshot for you to download. Your uploaded source code and associated launch data are encrypted in transit and at rest and retained for up to 48 hours for recovery. Your data is never used to train AI models. We exclude Git history, Git-ignored files, and files matching common sensitive-file patterns. Sensitive-file filtering is best effort; review your project for secrets. Continue? Or reply with [otherRegions regionName] to run the migration there instead.

Fill in the region fields from resolve-region, verbatim, and offer each otherRegions entry as an alternative. If the agent is connected to an AWS MCP server, pass that server's URL verbatim. The script reads the region from it and decides whether it counts as a signal.

python3 scripts/launch_with_aws.py resolve-region [--aws-mcp-url https://aws-mcp.<region>.api.aws/mcp]

Returns {"region": ..., "regionName": ..., "source": ..., "sourceLabel": ..., "baseUrl": ..., "otherRegions": [{"region": ..., "regionName": ...}]}.

Do NOT call create-launch until the user explicitly confirms; a missing or ambiguous response means no. Naming one of otherRegions is both a region choice and approval — pass its region as --region and do not ask again.

python3 scripts/launch_with_aws.py create-launch <app-directory> [name] --region <region>

Zips the directory, uploads it, then creates the launch. Returns the full launch object including launch.launchId and the region it was created in, which becomes the user's saved default. A launch exists only in its own region — pass its --region on every later command.

The launch starts in analyzing status and automatically progresses through analysis and planning.

3. Poll Launch Status
python3 scripts/launch_with_aws.py get-launch-status <launch-id> --region <region>

Poll until status is planned (ready for execution) or failed; report any other status to the user. Key status progression:

  • analyzing → detecting app type and dependencies

  • planning → generating migration plan

  • planned → ready for execution

  • executing → deployment in progress

  • completed → done

  • failed → check failureReason

4. Get Full Launch Details & Confirm
python3 scripts/launch_with_aws.py get-launch <launch-id> plan,cost_estimate --region <region>

Get full launch details. Optional second argument is a comma-separated include list: analysis, plan, execution, cost_estimate, download_url.

Present the cost estimate and plan to the user. The costEstimate field in the response contains estimatedMonthlyCost, region, and a services breakdown with per-service costs.

Confirmation Gate — present and wait for explicit approval:

Migration Summary

  • App type: [detected type from analysis]

  • Architecture: [target architecture from plan]

  • Estimated monthly cost: $X.XX/month ([costEstimate.region] pricing)

  • Migration runs in: [regionName], [region]

Ready to proceed? This will execute the migration in an AWS-managed environment (no cost to you) and produce the migrated snapshot for you to download.

Do NOT call start-launch-execution until the user explicitly confirms.

5. Start Execution
python3 scripts/launch_with_aws.py start-launch-execution <launch-id> --region <region>

Starts deployment. Then poll with get-launch-status until status is completed or failed. Sleep at least 30 seconds between polls.

6. Download
python3 scripts/launch_with_aws.py get-launch-download-url <launch-id> --region <region>

Always present the full download URL to the user — they may need it to download the migrated snapshot directly or for reference.

7. List or Delete Launches
python3 scripts/launch_with_aws.py list-launches
python3 scripts/launch_with_aws.py delete-launch <launch-id> --region <region>

list-launches queries both regions and returns items with a region on each launch; add --region to query one. A region that cannot be reached is reported under errors instead of failing the command, unless every queried region fails, in which case the command exits with that error.

8. Post-Migration: Apply Migrated Code Locally

After obtaining the download URL (adapt commands for the user's platform if not POSIX):

Step A: Download and unpack
curl -L -o /tmp/migration-snapshot.zip "<download_url>"
mkdir -p /tmp/migration-output
unzip -o /tmp/migration-snapshot.zip -d /tmp/migration-output
Step B: Prepare the local workspace

Ensure the user's working directory is clean:

cd <user-app-directory>
git status

If there are uncommitted changes, ask the user to commit or stash first. Do NOT proceed with a dirty working tree.

Step C: Apply migration (3-way merge)

Create a migration branch and overlay the migrated files:

cd <user-app-directory>
git checkout -b aws-migration
rsync -a /tmp/migration-output/ .
git status
git diff --stat

Review the changes with the user. Key additions to highlight:

  • aws-blocks/ — AWS Blocks infrastructure definition

  • DEPLOY.md — deployment instructions

  • Any modified config files

If there are conflicts with the user's existing files, present them and ask how to resolve.

Step D: Follow DEPLOY.md

Read the DEPLOY.md file in the project root and follow its instructions to deploy the app to the user's AWS account. Typical steps:

  1. AWS authentication (aws login --profile aws-migrate --region us-east-1)

  2. CDK bootstrap (first-time only): npm install && npx cdk bootstrap

  3. Deploy: npx cdk deploy --all --progress events

  4. Verify the CloudFront URL that CDK prints on completion.

Important: Always read DEPLOY.md from the migrated output — it is generated specifically for this app and architecture. Do not assume deployment steps from memory.

Unsupported Application Handling

If a launch fails during analysis with a failureReason indicating an unsupported app type (or the user's stack doesn't match the supported list):

  1. Tell the user: "This app type isn't directly supported by Launch with AWS yet. Let me search for other skills that can help deploy this kind of application."

  2. Search for relevant skills based on the app type (e.g. aws-serverless, aws-containers, aws-database, aws-deployment, aws-cdk, aws-ai-ml).

Files

scripts/auth.py

# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

"""IAM Identity Center PKCE authentication.

Uses SSO OIDC directly:
    1. RegisterClient -> obtain clientId/clientSecret
    2. PKCE authorize via browser -> capture auth code on loopback
    3. CreateToken (authorization_code grant) -> access + refresh tokens
    4. CreateToken (refresh_token grant) -> silent refresh

Tokens cached in ~/.launch-with-aws/session.json (mode 0600).
"""

import base64
import hashlib
import html
import json
import os
import secrets
import signal
import subprocess
import sys
import threading
import time
from dataclasses import asdict
from http.server import BaseHTTPRequestHandler
from typing import Any, Optional, Tuple
from urllib.parse import parse_qs, urlencode, urlparse

from launch_config import (
    AUTH_WAIT_POLL_INTERVAL_SECS,
    CALLBACK_TIMEOUT_SECS,
    CLIENT_NAME,
    DEFAULT_TOKEN_LIFETIME_SECS,
    ENV_SCOPES,
    MAX_SESSION_LIFETIME_SECS,
    SCOPES,
    SESSION_DIR,
    SESSION_FILE_NAME,
    SSO_OIDC_REGION,
    TOKEN_EXPIRY_BUFFER_SECS,
    ClientCredentials,
    StoredSession,
    resolve_issuer_url,
    write_private_file,
)

__version__ = "0.1.0"


class SessionExpiredError(Exception):
    """Raised when the session cannot be refreshed non-interactively."""

    pass


def _session_dir() -> str:
    return os.path.expanduser(SESSION_DIR)


def _session_file() -> str:
    return os.path.join(_session_dir(), SESSION_FILE_NAME)


def _create_sso_oidc_client(region: str) -> Any:
    import boto3
    from botocore.config import Config as BotoConfig

    config = BotoConfig(user_agent_extra=f"md/awslabs#launch-with-aws#{__version__}")
    return boto3.client("sso-oidc", region_name=region, config=config)


def _generate_pkce() -> Tuple[str, str]:
    code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b"=").decode("ascii")
    digest = hashlib.sha256(code_verifier.encode("ascii")).digest()
    code_challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
    return code_verifier, code_challenge


def _generate_state() -> str:
    return secrets.token_hex(16)


# ── Session persistence ──────────────────────────────────────────────────


def load_session() -> Optional[StoredSession]:
    try:
        with open(_session_file()) as f:
            return StoredSession.from_json(f.read())
    except (OSError, ValueError, TypeError, KeyError):
        return None


def save_session(session: StoredSession) -> None:
    write_private_file(_session_file(), session.to_json(indent=2))


def has_valid_session() -> bool:
    session = load_session()
    return bool(session and session.token_expires_at > int(time.time()) + TOKEN_EXPIRY_BUFFER_SECS)


# ── Client registration ──────────────────────────────────────────────────


def _resolve_scopes() -> list[str]:
    env_val = os.environ.get(ENV_SCOPES)
    if env_val:
        return [s.strip() for s in env_val.split(",") if s.strip()]
    return list(SCOPES)


def _register_client() -> ClientCredentials:
    sso_oidc = _create_sso_oidc_client(SSO_OIDC_REGION)
    scopes = _resolve_scopes()

    kwargs: dict[str, Any] = {
        "clientName": CLIENT_NAME,
        "clientType": "public",
        "grantTypes": ["authorization_code", "refresh_token"],
        "scopes": scopes,
        "issuerUrl": resolve_issuer_url(),
        "redirectUris": ["http://127.0.0.1"],
    }

    response = sso_oidc.register_client(**kwargs)

    client_id = response.get("clientId")
    client_secret = response.get("clientSecret")
    if not client_id or not client_secret:
        raise RuntimeError("Client registration response missing credentials")

    # Cap the local session lifetime at MAX_SESSION_LIFETIME_SECS. A
    # clientSecretExpiresAt of 0 means the IdP set no expiry; otherwise use the
    # sooner of the IdP value and the cap.
    expires_at_sec = response.get("clientSecretExpiresAt") or 0
    now = int(time.time())
    cap = now + MAX_SESSION_LIFETIME_SECS
    client_expires_at = cap if expires_at_sec == 0 else min(int(expires_at_sec), cap)

    return ClientCredentials(
        client_id=client_id,
        client_secret=client_secret,
        client_expires_at=client_expires_at,
        authorize_endpoint=f"https://oidc.{SSO_OIDC_REGION}.amazonaws.com/authorize",
        token_endpoint=f"https://oidc.{SSO_OIDC_REGION}.amazonaws.com/token",
        scopes=scopes,
    )


# ── Token exchange ────────────────────────────────────────────────────────


def _exchange_code(
    credentials: ClientCredentials,
    code: str,
    code_verifier: str,
    redirect_uri: str,
) -> Tuple[str, str, int]:
    sso_oidc = _create_sso_oidc_client(SSO_OIDC_REGION)
    response = sso_oidc.create_token(
        clientId=credentials.client_id,
        clientSecret=credentials.client_secret,
        grantType="authorization_code",
        code=code,
        codeVerifier=code_verifier,
        redirectUri=redirect_uri,
    )

    access_token = response.get("accessToken")
    refresh_token = response.get("refreshToken")
    if not access_token or not refresh_token:
        raise RuntimeError("Token exchange response missing tokens")

    expires_in = response.get("expiresIn") or DEFAULT_TOKEN_LIFETIME_SECS
    return access_token, refresh_token, int(time.time()) + expires_in


def _refresh_token(session: StoredSession) -> Tuple[str, str, int]:
    sso_oidc = _create_sso_oidc_client(SSO_OIDC_REGION)
    response = sso_oidc.create_token(
        clientId=session.client_id,
        clientSecret=session.client_secret,
        grantType="refresh_token",
        refreshToken=session.refresh_token,
    )

    access_token = response.get("accessToken")
    if not access_token:
        raise RuntimeError("Token refresh response missing tokens")

    refresh_token = response.get("refreshToken") or session.refresh_token
    expires_in = response.get("expiresIn") or DEFAULT_TOKEN_LIFETIME_SECS
    return access_token, refresh_token, int(time.time()) + expires_in


# ── Loopback callback server ─────────────────────────────────────────────


class _OAuthState:
    def __init__(self, expected_state: str) -> None:
        self.expected_state = expected_state
        self.auth_code: Optional[str] = None
        self.error: Optional[str] = None
        self.code_received = threading.Event()


class _CallbackHandler(BaseHTTPRequestHandler):
    def _respond(self, status: int, message: str) -> None:
        self.send_response(status)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.end_headers()
        body = f'<html><head><meta charset="utf-8"></head><body><h2>{html.escape(message)}</h2></body></html>'
        self.wfile.write(body.encode("utf-8"))

    def do_GET(self) -> None:  # noqa: N802
        state: _OAuthState = self.server.oauth_state  # type: ignore[attr-defined]
        params = parse_qs(urlparse(self.path).query)

        error = params.get("error", [None])[0]
        if error:
            desc = params.get("error_description", [error])[0]
            self._respond(400, f"Authorization failed: {desc}")
            state.error = f"Authorization denied: {desc}"
            state.code_received.set()
            return

        returned_state = params.get("state", [None])[0]
        code = params.get("code", [None])[0]

        if returned_state != state.expected_state:
            if not code:
                self.send_response(204)
                self.end_headers()
                return
            self._respond(400, "State mismatch")
            state.error = "OAuth state mismatch - possible CSRF"
            state.code_received.set()
            return

        if not code:
            self.send_response(204)
            self.end_headers()
            return

        self._respond(200, "Authenticated - you can close this tab.")
        state.auth_code = code
        state.code_received.set()

    def log_message(self, format: str, *args: object) -> None:
        pass


# ── Public API ────────────────────────────────────────────────────────────


def get_access_token() -> str:
    """Return a valid Bearer access token (non-interactive).

    Tries the cached token and silent refresh. If neither works, raises
    SessionExpiredError so the caller can direct the user to auth-start.
    """
    session = load_session()
    now = int(time.time())

    # 1. Valid cached access token.
    if session and session.token_expires_at > now + TOKEN_EXPIRY_BUFFER_SECS:
        return session.access_token

    # 2. Try silent refresh.
    if session and session.client_expires_at > now:
        try:
            access_token, refresh_tok, token_expires_at = _refresh_token(session)
            updated = StoredSession(
                **{
                    **asdict(session),
                    "access_token": access_token,
                    "refresh_token": refresh_tok,
                    "token_expires_at": token_expires_at,
                }
            )
            save_session(updated)
            return updated.access_token
        except Exception:
            pass

    raise SessionExpiredError("Session expired or not authenticated. Run auth-start to sign in.")


def session_status() -> dict:
    """Report the local session state without triggering authentication.

    Returns a dict describing whether a session exists, whether its access
    token is currently usable, and how long until the access token and the
    overall session registration expire (seconds, clamped at 0).
    """
    session = load_session()
    if not session:
        return {"authenticated": False}

    now = int(time.time())
    token_valid = session.token_expires_at > now + TOKEN_EXPIRY_BUFFER_SECS
    return {
        "authenticated": token_valid,
        "tokenExpiresInSecs": max(0, session.token_expires_at - now),
        "sessionExpiresInSecs": max(0, session.client_expires_at - now),
        "canRefresh": session.client_expires_at > now,
    }


def sign_out() -> dict:
    """Sign out: delete the local session so the next call requires re-auth.

    Best-effort revokes the refresh token, then removes the local session file.
    Revocation failures do not block local deletion.
    """
    session = load_session()

    if session and session.refresh_token:
        try:
            sso_oidc = _create_sso_oidc_client(SSO_OIDC_REGION)
            # revoke_token is not exposed on all endpoints; call it only when
            # available.
            revoke = getattr(sso_oidc, "revoke_token", None)
            if callable(revoke):
                revoke(
                    clientId=session.client_id,
                    clientSecret=session.client_secret,
                    token=session.refresh_token,
                    tokenTypeHint="refresh_token",
                )
        except Exception:
            pass

    removed = False
    try:
        os.unlink(_session_file())
        removed = True
    except FileNotFoundError:
        pass

    return {"signedOut": True, "sessionRemoved": removed}


# ── Non-blocking auth (auth-start / auth-wait) ──────────────────────────


def start_auth() -> dict:
    """Attempt auth non-blockingly. Returns immediately.

    Returns:
      - {authenticated: True, reusedCachedSession: True} if cached token valid
      - {authenticated: True, reusedCachedSession: False} if silent refresh worked
      - {authenticated: False, signInUrl: ..., pid: N, port: N} if interactive needed
    """
    session = load_session()
    now = int(time.time())

    if session and session.token_expires_at > now + TOKEN_EXPIRY_BUFFER_SECS:
        return {"authenticated": True, "reusedCachedSession": True}

    if session and session.client_expires_at > now:
        try:
            access_token, refresh_tok, token_expires_at = _refresh_token(session)
            updated = StoredSession(
                **{
                    **asdict(session),
                    "access_token": access_token,
                    "refresh_token": refresh_tok,
                    "token_expires_at": token_expires_at,
                }
            )
            save_session(updated)
            return {"authenticated": True, "reusedCachedSession": False}
        except Exception:
            pass

    credentials: ClientCredentials
    if session and session.client_expires_at > now:
        credentials = session
    else:
        credentials = _register_client()

    code_verifier, code_challenge = _generate_pkce()
    state_value = _generate_state()

    server_script = os.path.join(os.path.dirname(__file__), "auth_callback_server.py")
    # Configuration values are passed to the child over stdin as a JSON blob.
    proc = subprocess.Popen(
        [
            sys.executable,
            server_script,
            "--state",
            state_value,
            "--timeout",
            str(int(CALLBACK_TIMEOUT_SECS)),
        ],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        stdin=subprocess.PIPE,
        start_new_session=True,
    )

    # Write the config to the child over stdin, then close it so the child sees EOF.
    assert proc.stdin is not None
    proc.stdin.write(
        json.dumps(
            {
                "client_id": credentials.client_id,
                "client_secret": credentials.client_secret,
                "client_expires_at": credentials.client_expires_at,
                "scopes": credentials.scopes,
                "code_verifier": code_verifier,
            }
        ).encode()
    )
    proc.stdin.close()

    # Child writes port to stdout once bound.
    assert proc.stdout is not None
    assert proc.stderr is not None
    port_line = proc.stdout.readline().decode().strip()
    proc.stdout.close()
    if not port_line.isdigit():
        proc.terminate()
        err_output = proc.stderr.read().decode().strip()
        proc.stderr.close()
        detail = err_output or port_line or "no output"
        raise RuntimeError(f"Callback server failed to start: {detail}")
    proc.stderr.close()
    port = int(port_line)

    redirect_uri = f"http://127.0.0.1:{port}"
    authorize_url = f"{credentials.authorize_endpoint}?" + urlencode(
        {
            "response_type": "code",
            "client_id": credentials.client_id,
            "redirect_uri": redirect_uri,
            "state": state_value,
            "code_challenge": code_challenge,
            "code_challenge_method": "S256",
            "scope": " ".join(credentials.scopes),
        }
    )

    return {
        "authenticated": False,
        "signInUrl": authorize_url,
        "pid": proc.pid,
        "port": port,
    }


def wait_for_auth(pid: int, timeout: Optional[float] = None) -> dict:
    """Block until the background callback server completes auth.

    Args:
        pid: PID of the background callback server (from start_auth).
        timeout: Max seconds to wait (default: CALLBACK_TIMEOUT_SECS).

    Returns:
        {authenticated: True} on success.

    Raises:
        TimeoutError if timeout exceeded.
        RuntimeError if the server exited without completing auth.
    """
    timeout = timeout or CALLBACK_TIMEOUT_SECS
    deadline = time.time() + timeout

    while time.time() < deadline:
        if has_valid_session():
            return {"authenticated": True}

        try:
            os.kill(pid, 0)
        except OSError:
            if has_valid_session():
                return {"authenticated": True}
            raise RuntimeError(
                "Auth callback server exited without completing authentication. "
                "Run auth-start again."
            )

        time.sleep(AUTH_WAIT_POLL_INTERVAL_SECS)

    try:
        os.kill(pid, signal.SIGTERM)
    except OSError:
        pass
    raise TimeoutError(f"Authentication timed out after {int(timeout)} seconds")

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an MCP server, Needs a command-line tool, Needs Python, Needs Node.js

Security

What our scanner noticed· Medium risk

  • Phrasing that asks for actions without confirmation
  • References to credential files
  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
AWS
Publisher
AWS (Official)
Source
aws/agent-toolkit-for-aws · skills/core-skills/launch-with-aws · commit 0a218f8
Licence
Apache License 2.0 (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • NOTICE (added by AutoGineer from NOTICE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced
Also distributed in
aws/agent-toolkit-for-aws

AutoGineer is not affiliated with or endorsed by AWS.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download launch-with-aws.zip