Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 105 additions & 6 deletions install/bash/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ set -euo pipefail
# Base URL for additional scripts
CODEPLAIN_SCRIPTS_BASE_URL="${CODEPLAIN_SCRIPTS_BASE_URL:-https://codeplain.ai}"

# Base URL for the Codeplain API (used to verify the API key)
CODEPLAIN_API_URL="${CODEPLAIN_API_URL:-https://api.codeplain.ai}"

# Brand Colors (True Color / 24-bit)
YELLOW='\033[38;2;224;255;110m' # #E0FF6E
GREEN='\033[38;2;121;252;150m' # #79FC96
Expand Down Expand Up @@ -39,6 +42,30 @@ install_uv() {
export PATH="$HOME/.local/bin:$PATH"
}

# Trim leading/trailing whitespace (spaces, tabs, newlines, carriage returns).
# Users often copy the API key with surrounding whitespace or newlines.
trim_whitespace() {
local value="$*"
value="${value#"${value%%[![:space:]]*}"}" # strip leading whitespace
value="${value%"${value##*[![:space:]]}"}" # strip trailing whitespace
printf '%s' "$value"
}

# Verify an API key against the Codeplain API's /status endpoint.
# Sets the global VALIDATION_HTTP_CODE and returns 0 only when the key is valid
# (HTTP 200). This checks only the API key, nothing else about the install.
validate_api_key() {
local key="$1"
local http_code
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
--max-time 30 \
-X POST "${CODEPLAIN_API_URL}/status" \
-H "Content-Type: application/json" \
--data "{\"api_key\":\"${key}\"}" 2>/dev/null || true)
VALIDATION_HTTP_CODE="${http_code:-000}"
[ "$VALIDATION_HTTP_CODE" = "200" ]
}

# Check if uv is installed
if ! command -v uv &> /dev/null; then
echo -e "${GRAY}uv is not installed.${NC}"
Expand Down Expand Up @@ -75,10 +102,11 @@ fi

# Check if API key already exists
SKIP_API_KEY_SETUP=false
API_KEY_VERIFIED=false
if [ -n "${CODEPLAIN_API_KEY:-}" ]; then
USE_EXISTING=false
if [ "$NONINTERACTIVE" = "1" ]; then
echo -e "${GREEN}✓${NC} Using existing CODEPLAIN_API_KEY (non-interactive mode)."
SKIP_API_KEY_SETUP=true
USE_EXISTING=true
else
echo -e " You already have an API key configured."
echo ""
Expand All @@ -88,8 +116,33 @@ if [ -n "${CODEPLAIN_API_KEY:-}" ]; then
echo ""

if [[ ! "$GET_NEW_KEY" =~ ^[Yy]$ ]]; then
USE_EXISTING=true
fi
fi

# Verify the existing key before trusting it, so a stale/expired key
# doesn't slip through with a false "Sign in successful".
if [ "$USE_EXISTING" = true ]; then
echo -e "${GRAY}Verifying your existing API key...${NC}"
if validate_api_key "$CODEPLAIN_API_KEY"; then
echo -e "${GREEN}✓${NC} Using existing API key."
SKIP_API_KEY_SETUP=true
API_KEY_VERIFIED=true
elif [ "$VALIDATION_HTTP_CODE" = "401" ]; then
if [ "$NONINTERACTIVE" = "1" ]; then
echo -e "${RED}Existing CODEPLAIN_API_KEY is invalid.${NC}"
echo -e "${GRAY}Set a valid CODEPLAIN_API_KEY and re-run the installer.${NC}"
SKIP_API_KEY_SETUP=true
else
echo -e "${RED}Your existing API key is invalid. Please enter a new one.${NC}"
echo ""
# SKIP_API_KEY_SETUP stays false: fall through to the paste flow.
fi
else
# Could not reach the API. Keep the existing key rather than
# blocking the user over a transient network issue.
echo -e "${GRAY}Could not verify the existing API key (could not reach ${CODEPLAIN_API_URL}). Keeping it.${NC}"
SKIP_API_KEY_SETUP=true
fi
fi
fi
Expand All @@ -100,8 +153,34 @@ if [ "$SKIP_API_KEY_SETUP" = false ]; then
else
echo -e "Go to ${YELLOW}https://platform.codeplain.ai${NC} and sign up to get your API key."
echo ""
read -r -p "Paste your API key here: " API_KEY < /dev/tty
echo ""
# Keep prompting until we get a valid API key (or the user submits an
# empty value to skip). The pasted key is trimmed and verified against
# the Codeplain API before we accept it.
while true; do
read -r -p "Paste your API key here: " API_KEY < /dev/tty
echo ""
API_KEY="$(trim_whitespace "$API_KEY")"

# Empty input: let the user skip key setup for now.
if [ -z "$API_KEY" ]; then
break
fi

echo -e "${GRAY}Verifying your API key...${NC}"
if validate_api_key "$API_KEY"; then
echo -e "${GREEN}✓${NC} API key verified."
echo ""
API_KEY_VERIFIED=true
break
elif [ "$VALIDATION_HTTP_CODE" = "401" ]; then
echo -e "${RED}Invalid API key. Please make sure the full key was copied.${NC}"
echo ""
else
echo -e "${RED}Could not verify the API key (could not reach ${CODEPLAIN_API_URL}).${NC}"
echo -e "${GRAY}Check your internet connection and try again.${NC}"
echo ""
fi
done
fi
fi

Expand Down Expand Up @@ -159,8 +238,11 @@ cat << 'EOF'
|_|
EOF
echo ""
echo -e "${GREEN}✓ Sign in successful.${NC}"
echo ""
# Only claim success when a verified API key is actually configured.
if [ "$API_KEY_VERIFIED" = true ]; then
echo -e "${GREEN}✓ Sign in successful.${NC}"
echo ""
fi
echo -e " ${WHITE}Welcome to *codeplain!${NC}"
echo ""
echo -e " ${GRAY}Spec-driven, production-ready code generation${NC}"
Expand Down Expand Up @@ -307,6 +389,23 @@ if [[ ! "${DOWNLOAD_EXAMPLES:-}" =~ ^[Nn]$ ]]; then
run_script "examples.sh"
fi

# Final verification: make sure the installed codeplain can actually run and
# reach the API. Only meaningful when an API key is configured, so users who
# skipped the key are not falsely told something went wrong.
if [ -n "${CODEPLAIN_API_KEY:-}" ]; then
# Ensure the freshly installed tool is findable in this session.
export PATH="$HOME/.local/bin:$PATH"
echo -e "${GRAY}Verifying your installation...${NC}"
if codeplain --status > /dev/null 2>&1; then
echo -e "${GREEN}✓${NC} Installation verified."
else
echo -e "${RED}Something went wrong during installation.${NC}"
echo -e "${GRAY}Please restart your terminal and try again, or reinstall with:${NC}"
echo -e " uv tool install --force codeplain"
exit 1
fi
fi

# Final message
if [ "$NONINTERACTIVE" != "1" ]; then
clear
Expand Down
122 changes: 116 additions & 6 deletions install/powershell/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ if (-not $env:CODEPLAIN_SCRIPTS_BASE_URL) {
$env:CODEPLAIN_SCRIPTS_BASE_URL = "https://codeplain.ai"
}

# Base URL for the Codeplain API (used to verify the API key)
if (-not $env:CODEPLAIN_API_URL) {
$env:CODEPLAIN_API_URL = "https://api.codeplain.ai"
}

# Brand Colors (True Color / 24-bit)
$ESC = [char]27
$YELLOW = "$ESC[38;2;224;255;110m" # #E0FF6E
Expand Down Expand Up @@ -56,6 +61,33 @@ function Install-Uv {
}
}

# Verify an API key against the Codeplain API's /status endpoint.
# Returns a status string: "valid" (HTTP 200), "invalid" (HTTP 401), or
# "error" (could not reach the API). This checks only the API key.
function Test-ApiKey {
param([string]$Key)

$body = @{ api_key = $Key } | ConvertTo-Json -Compress
try {
$response = Invoke-WebRequest -Uri "$($env:CODEPLAIN_API_URL)/status" `
-Method Post `
-ContentType "application/json" `
-Body $body `
-TimeoutSec 30 `
-UseBasicParsing `
-ErrorAction Stop
if ($response.StatusCode -eq 200) { return "valid" }
return "error"
} catch {
$statusCode = $null
if ($_.Exception.Response) {
$statusCode = [int]$_.Exception.Response.StatusCode
}
if ($statusCode -eq 401) { return "invalid" }
return "error"
}
}

# Check if uv is installed
if (-not (Get-Command uv -ErrorAction SilentlyContinue)) {
Write-Host "${GRAY}uv is not installed.${NC}"
Expand Down Expand Up @@ -125,10 +157,11 @@ Write-Host ""

# Check if API key already exists
$skipApiKeySetup = $false
$apiKeyVerified = $false
if ($env:CODEPLAIN_API_KEY) {
$useExisting = $false
if ($nonInteractive) {
Write-Host "${GREEN}✓${NC} Using existing CODEPLAIN_API_KEY (non-interactive mode)."
$skipApiKeySetup = $true
$useExisting = $true
} else {
Write-Host " You already have an API key configured."
Write-Host ""
Expand All @@ -138,8 +171,34 @@ if ($env:CODEPLAIN_API_KEY) {
Write-Host ""

if ($getNewKey -notmatch '^[Yy]$') {
$useExisting = $true
}
}

# Verify the existing key before trusting it, so a stale/expired key
# doesn't slip through with a false "Sign in successful".
if ($useExisting) {
Write-Host "${GRAY}Verifying your existing API key...${NC}"
$existingResult = Test-ApiKey $env:CODEPLAIN_API_KEY
if ($existingResult -eq "valid") {
Write-Host "${GREEN}✓${NC} Using existing API key."
$skipApiKeySetup = $true
$apiKeyVerified = $true
} elseif ($existingResult -eq "invalid") {
if ($nonInteractive) {
Write-Host "${RED}Existing CODEPLAIN_API_KEY is invalid.${NC}"
Write-Host "${GRAY}Set a valid CODEPLAIN_API_KEY and re-run the installer.${NC}"
$skipApiKeySetup = $true
} else {
Write-Host "${RED}Your existing API key is invalid. Please enter a new one.${NC}"
Write-Host ""
# $skipApiKeySetup stays false: fall through to the paste flow.
}
} else {
# Could not reach the API. Keep the existing key rather than
# blocking the user over a transient network issue.
Write-Host "${GRAY}Could not verify the existing API key (could not reach $($env:CODEPLAIN_API_URL)). Keeping it.${NC}"
$skipApiKeySetup = $true
}
}
}
Expand All @@ -151,8 +210,34 @@ if (-not $skipApiKeySetup) {
} else {
Write-Host "Go to ${YELLOW}https://platform.codeplain.ai${NC} and sign up to get your API key."
Write-Host ""
$apiKey = Read-Host "Paste your API key here"
Write-Host ""
# Keep prompting until we get a valid API key (or the user submits an
# empty value to skip). The pasted key is trimmed and verified against
# the Codeplain API before we accept it.
while ($true) {
$apiKey = (Read-Host "Paste your API key here").Trim()
Write-Host ""

# Empty input: let the user skip key setup for now.
if (-not $apiKey) {
break
}

Write-Host "${GRAY}Verifying your API key...${NC}"
$result = Test-ApiKey $apiKey
if ($result -eq "valid") {
Write-Host "${GREEN}✓${NC} API key verified."
Write-Host ""
$apiKeyVerified = $true
break
} elseif ($result -eq "invalid") {
Write-Host "${RED}Invalid API key. Please make sure the full key was copied.${NC}"
Write-Host ""
} else {
Write-Host "${RED}Could not verify the API key (could not reach $($env:CODEPLAIN_API_URL)).${NC}"
Write-Host "${GRAY}Check your internet connection and try again.${NC}"
Write-Host ""
}
}
}
}

Expand Down Expand Up @@ -181,8 +266,11 @@ Write-Host @'
|_|
'@
Write-Host ""
Write-Host "${GREEN}✓ Sign in successful.${NC}"
Write-Host ""
# Only claim success when a verified API key is actually configured.
if ($apiKeyVerified) {
Write-Host "${GREEN}✓ Sign in successful.${NC}"
Write-Host ""
}
Write-Host " ${WHITE}Welcome to *codeplain!${NC}"
Write-Host ""
Write-Host " ${GRAY}Spec-driven, production-ready code generation${NC}"
Expand Down Expand Up @@ -332,6 +420,28 @@ if ($downloadExamples -notmatch '^[Nn]$') {
Invoke-SubScript "examples.ps1"
}

# Final verification: make sure the installed codeplain can actually run and
# reach the API. Only meaningful when an API key is configured, so users who
# skipped the key are not falsely told something went wrong.
if ($env:CODEPLAIN_API_KEY) {
Write-Host "${GRAY}Verifying your installation...${NC}"
$verifyOk = $false
try {
& codeplain --status *> $null
$verifyOk = ($LASTEXITCODE -eq 0)
} catch {
$verifyOk = $false
}
if ($verifyOk) {
Write-Host "${GREEN}✓${NC} Installation verified."
} else {
Write-Host "${RED}Something went wrong during installation.${NC}"
Write-Host "${GRAY}Please restart your terminal and try again, or reinstall with:${NC}"
Write-Host " uv tool install --force codeplain"
exit 1
}
}

# Final message
if (-not $nonInteractive) { Clear-Host }
Write-Host ""
Expand Down
3 changes: 2 additions & 1 deletion plain2code.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ def main(): # noqa: C901
console.error(
"Your API key is required. Please set the CODEPLAIN_API_KEY environment variable or provide it with the --api-key argument.\n"
)
return
sys.exit(1)

if not args.api:
args.api = "https://api.codeplain.ai"
Expand All @@ -336,6 +336,7 @@ def main(): # noqa: C901
print_status(args.api_key, args.api, system_config.client_version)
except Exception as e:
console.error(f"Error fetching status: {str(e)}")
sys.exit(1)
return

template_dirs = file_utils.get_template_directories(args.filename, args.template_dir, DEFAULT_TEMPLATE_DIRS)
Expand Down
Loading