diff --git a/README.md b/README.md index b83359e..427be8c 100644 --- a/README.md +++ b/README.md @@ -1,143 +1,107 @@ -# OSMAP CLI - Embed Map Navigator & Token Manager +# OSMAP Embed Map & Token CLI -A lightweight CLI tool for developers and administrators to manage authentication tokens and quickly navigate or launch OSMAP KAI embed map routes across different environments (`live`, `beta`, `staging`, `demo`, `local`). +Unified CLI utility and API reference for generating embed tokens and URLs for all **OSMAP** domains: +1. **`app.osmap.id`** (Share-Map Token API) +2. **`live.osmap.id`** (KAI Live App Token) +3. **`beta.osmap.id`** (KAI Beta App Token) --- -## ⚡ Quick Start (No Installation Needed) +## 🚀 cURL Reference by Domain -### 1. Prerequisites -- **Bash** (`Git Bash`, `WSL`, `Linux`, or `macOS`) -- **`curl`** -- **`jq`** (JSON processor) - - **Windows (PowerShell as Admin)**: `choco install jq -y` - - **Ubuntu / Debian / WSL**: `sudo apt update && sudo apt install -y jq` - - **macOS**: `brew install jq` +### 1. New Share-Map System (`app.osmap.id`) +**API Endpoint**: `POST https://api.osmap.id/apps/token/share-map` +**Embed Format**: `https://app.osmap.id/maps/embed/?token=` ---- - -## 🚀 Running the CLI - -### Option A: Direct Execution via `curl` - -#### Using Git Bash / Linux / macOS: +#### Map 1: Accelerometer (Map ID: 94) ```bash -curl -sSL https://git.oslog.pro/dandy/osmap_cli/raw/branch/main/osmap-embed.sh | bash +curl -X POST "https://api.osmap.id/apps/token/share-map" \ + -H "Content-Type: application/json" \ + -d '{"app_id":"6f728437e6c4d361fb811913b8acd70e","app_secret":"ab9419a7fed7c51a6ff6daf501d408f2aed25c0ccd148a6c18b76f54949f55d8","expires_in":86400,"map_id":94}' ``` -To pass arguments / flags: +#### Map 2: ROCC (Map ID: 46) ```bash -# Open Executive Map directly in browser -curl -sSL https://git.oslog.pro/dandy/osmap_cli/raw/branch/main/osmap-embed.sh | bash -s -- executive --env live --open - -# Get all embed URLs for beta environment -curl -sSL https://git.oslog.pro/dandy/osmap_cli/raw/branch/main/osmap-embed.sh | bash -s -- all --env beta +curl -X POST "https://api.osmap.id/apps/token/share-map" \ + -H "Content-Type: application/json" \ + -d '{"app_id":"6f728437e6c4d361fb811913b8acd70e","app_secret":"ab9419a7fed7c51a6ff6daf501d408f2aed25c0ccd148a6c18b76f54949f55d8","expires_in":86400,"map_id":46}' ``` -#### Using Windows PowerShell: -```powershell -bash -c "curl -sSL https://git.oslog.pro/dandy/osmap_cli/raw/branch/main/osmap-embed.sh | bash" +#### Map 3: Genset (Map ID: 74) +```bash +curl -X POST "https://api.osmap.id/apps/token/share-map" \ + -H "Content-Type: application/json" \ + -d '{"app_id":"6f728437e6c4d361fb811913b8acd70e","app_secret":"ab9419a7fed7c51a6ff6daf501d408f2aed25c0ccd148a6c18b76f54949f55d8","expires_in":86400,"map_id":74}' +``` + +#### Map 4: RCS (Map ID: 72) +```bash +curl -X POST "https://api.osmap.id/apps/token/share-map" \ + -H "Content-Type: application/json" \ + -d '{"app_id":"6f728437e6c4d361fb811913b8acd70e","app_secret":"ab9419a7fed7c51a6ff6daf501d408f2aed25c0ccd148a6c18b76f54949f55d8","expires_in":86400,"map_id":72}' +``` + +#### Map 5: JPL (Map ID: 70) +```bash +curl -X POST "https://api.osmap.id/apps/token/share-map" \ + -H "Content-Type: application/json" \ + -d '{"app_id":"2ee1aa4876f6b7fa3c43c75ca1c83139","app_secret":"073d0929a74bb664c402de98d24153c1f9f2f648d59c381f2fef3c32610fa174","expires_in":86400,"map_id":70}' ``` --- -### Option B: Clone & Run Locally +### 2. OSMAP KAI System (`live.osmap.id`) +**API Endpoint**: `POST https://live.osmap.id/api/apps/token` +**Embed Format**: `https://live.osmap.id/?token=` ```bash -git clone https://git.oslog.pro/dandy/osmap_cli.git -cd osmap_cli -chmod +x osmap-embed.sh +curl -X POST "https://live.osmap.id/api/apps/token" \ + -H "Content-Type: application/json" \ + -d '{ + "app_id": "IrSIcf06QRwUCmsRCZ0JvsVibSfuPUG4", + "app_secret": "Z8tPOBHGbwWu9TrIddRqkiDFDymtiPAhMNbSz6aUOOTkqdZoQYuTWXTra5m2mPQa", + "expires_in_hours": 24 + }' +``` -# Launch Interactive Menu +--- + +### 3. OSMAP KAI System (`beta.osmap.id`) +**API Endpoint**: `POST https://beta.osmap.id/api/apps/token` +**Embed Format**: `https://beta.osmap.id/?token=` + +```bash +curl -X POST "https://beta.osmap.id/api/apps/token" \ + -H "Content-Type: application/json" \ + -d '{ + "app_id": "IrSIcf06QRwUCmsRCZ0JvsVibSfuPUG4", + "app_secret": "Z8tPOBHGbwWu9TrIddRqkiDFDymtiPAhMNbSz6aUOOTkqdZoQYuTWXTra5m2mPQa", + "expires_in_hours": 24 + }' +``` + +--- + +## 🛠️ CLI Usage (`osmap-embed.sh`) + +```bash +chmod +x ./osmap-embed.sh +``` + +### Launch Interactive Navigator +```bash ./osmap-embed.sh ``` +*Allows selecting maps from `app.osmap.id` or routes from `live.osmap.id` / `beta.osmap.id` with live environment switching!* ---- +### Quick Commands -## 📖 Usage & Commands - -```bash -./osmap-embed.sh [route|command] [options] -``` - -### Commands - -| Command | Description | -| :--- | :--- | -| `interactive` *(default)* | Opens an interactive CLI menu to pick routes, copy tokens, or switch environments | -| `all` | Prints a formatted table of all map embed URLs with a valid token | -| `token` | Fetches and prints the raw active access token only | -| `validate` | Checks whether the locally cached token is still valid | -| `revoke` | Revokes active tokens on the server and clears local cache | -| `register` | Registers a new application and saves credentials | - -### Available Map Routes - -| Route Identifier | Map Name | Target Path | -| :--- | :--- | :--- | -| `executive` | Executive Map | `/executive` | -| `rolling` | Rolling Map | `/rolling` | -| `iris-jpl` | IRIS JPL | `/iris-jpl` | -| `iris-jpl-v2` | IRIS JPL (v2) | `/v2/iris-jpl` | -| `accelerometer` | Executive Accelerometer | `/executive/accelerometer` | -| `genset` | Genset Monitoring | `/genset` | -| `managerial` | Managerial / Asset View | `/managerial` | -| `fatigue` | Fatigue Monitoring | `/fatigue` | -| `sigap-tracker` | SIGAP Tracker | `/sigap-tracker` | -| `sigap-dashboard` | SIGAP Dashboard | `/sigap-dashboard` | -| `railway-cargo-system` | Railway Cargo System (RCS) | `/railway-cargo-system` | - ---- - -## ⚙️ Options & Flags - -| Flag | Description | Default | -| :--- | :--- | :--- | -| `-e`, `--env ` | Target environment (`live`, `beta`, `staging`, `demo`, `local`, or custom URL) | `live` | -| `-o`, `--open` | Automatically open the generated URL in your default browser | `false` | -| `-c`, `--copy` | Automatically copy the URL to system clipboard | `false` | -| `--app-id ` | Override App ID credential | Default Built-in / Env | -| `--app-secret ` | Override App Secret credential | Default Built-in / Env | -| `--save` | Save provided `--app-id` and `--app-secret` to `.osmap_credentials` | `false` | -| `-h`, `--help` | Show command line help and usage | - | - ---- - -## 💡 Examples - -- **Interactive Menu (live environment):** - ```bash - ./osmap-embed.sh - ``` - -- **Open IRIS JPL in browser on Live environment:** - ```bash - ./osmap-embed.sh iris-jpl --open - ``` - -- **Get the raw token string for scripts / automation:** - ```bash - TOKEN=$(./osmap-embed.sh token --env live) - echo "Current Token: $TOKEN" - ``` - -- **Open Managerial Map on Beta environment:** - ```bash - ./osmap-embed.sh managerial --env beta --open - ``` - -- **Test against Local development server (`http://localhost:8000`):** - ```bash - ./osmap-embed.sh --env local - ``` - ---- - -## 🔐 Credentials & Configuration Precedence - -Credentials are automatically loaded in the following order: -1. CLI Flags (`--app-id`, `--app-secret`) -2. Environment Variables (`OSMAP_APP_ID`, `OSMAP_APP_SECRET`) -3. Local config file (`.osmap_credentials`) -4. Current directory `.env` file -5. Built-in default credentials +| Goal | Command | +|---|---| +| Show all cURL commands | `./osmap-embed.sh curls` | +| View `app.osmap.id` embed URLs | `./osmap-embed.sh all` | +| View `live.osmap.id` embed URLs | `./osmap-embed.sh all --env live` | +| View `beta.osmap.id` embed URLs | `./osmap-embed.sh all --env beta` | +| Open map from `app.osmap.id` in browser | `./osmap-embed.sh accelerometer --open` | +| Open KAI route in browser | `./osmap-embed.sh executive --env live --open` | +| Get token string only | `./osmap-embed.sh token 94` or `./osmap-embed.sh token executive` | diff --git a/osmap-embed.sh b/osmap-embed.sh index d7ace8b..324d2cd 100644 --- a/osmap-embed.sh +++ b/osmap-embed.sh @@ -1,10 +1,11 @@ #!/usr/bin/env bash # ============================================================================== -# OSMAP KAI Embed Token & Map Navigator CLI +# OSMAP Embed Map & Token CLI (Multi-Domain Support) # ============================================================================== -# Automates token management (obtain, validate, cache, revoke) and provides -# access to OSMAP KAI protected embed map routes. +# Supports: +# 1. New Share-Map System -> app.osmap.id (API: api.osmap.id/apps/token/share-map) +# 2. OSMAP KAI System -> live.osmap.id & beta.osmap.id (API: /api/apps/token) # ============================================================================== set -e @@ -15,89 +16,74 @@ GREEN='\033[0;32m' BLUE='\033[0;34m' CYAN='\033[0;36m' YELLOW='\033[1;33m' +MAGENTA='\033[0;35m' BOLD='\033[1m' NC='\033[0m' # No Color -# Default values -DEFAULT_ENV="live" -DEFAULT_APP_ID="IrSIcf06QRwUCmsRCZ0JvsVibSfuPUG4" -DEFAULT_APP_SECRET="Z8tPOBHGbwWu9TrIddRqkiDFDymtiPAhMNbSz6aUOOTkqdZoQYuTWXTra5m2mPQa" -CONFIG_FILE=".osmap_credentials" +# Cache directory CACHE_DIR="${HOME}/.cache/osmap" mkdir -p "$CACHE_DIR" -# Route Definitions: "Route_Key|Display_Name|Path" -ROUTES=( +# Config file for saved credentials +CONFIG_FILE=".osmap_credentials" + +# ------------------------------------------------------------------------------ +# 1. NEW SHARE-MAP SYSTEM (app.osmap.id) +# ------------------------------------------------------------------------------ +APP_OSMAP_DOMAIN="app.osmap.id" +APP_OSMAP_API="https://api.osmap.id" +SHARE_MAP_EXPIRES_IN=86400 + +# Share Map Presets: "Key|Map_ID|Name|App_ID|App_Secret|Description" +SHARE_MAP_PRESETS=( + "accelerometer|94|Map Accelerometer|6f728437e6c4d361fb811913b8acd70e|ab9419a7fed7c51a6ff6daf501d408f2aed25c0ccd148a6c18b76f54949f55d8|Accelerometer Vibration & Track Monitoring" + "rocc|46|Map ROCC|6f728437e6c4d361fb811913b8acd70e|ab9419a7fed7c51a6ff6daf501d408f2aed25c0ccd148a6c18b76f54949f55d8|Railway Operations Control Center" + "genset|74|Map Genset|6f728437e6c4d361fb811913b8acd70e|ab9419a7fed7c51a6ff6daf501d408f2aed25c0ccd148a6c18b76f54949f55d8|Genset & Power Telemetry Monitoring" + "rcs|72|Map RCS|6f728437e6c4d361fb811913b8acd70e|ab9419a7fed7c51a6ff6daf501d408f2aed25c0ccd148a6c18b76f54949f55d8|Railway Cargo System Monitoring" + "jpl|70|Map JPL|2ee1aa4876f6b7fa3c43c75ca1c83139|073d0929a74bb664c402de98d24153c1f9f2f648d59c381f2fef3c32610fa174|Perlintasan Sebidang (JPL) Monitoring" +) +SHARE_FALLBACK_APP_ID="6f728437e6c4d361fb811913b8acd70e" +SHARE_FALLBACK_APP_SECRET="ab9419a7fed7c51a6ff6daf501d408f2aed25c0ccd148a6c18b76f54949f55d8" + +# ------------------------------------------------------------------------------ +# 2. OSMAP KAI SYSTEM (live.osmap.id & beta.osmap.id) +# ------------------------------------------------------------------------------ +DEFAULT_KAI_ENV="live" +DEFAULT_KAI_APP_ID="IrSIcf06QRwUCmsRCZ0JvsVibSfuPUG4" +DEFAULT_KAI_APP_SECRET="Z8tPOBHGbwWu9TrIddRqkiDFDymtiPAhMNbSz6aUOOTkqdZoQYuTWXTra5m2mPQa" + +KAI_ROUTES=( "executive|Executive Map|/executive" "rolling|Rolling Map|/rolling" "iris-jpl|IRIS JPL|/iris-jpl" "iris-jpl-v2|IRIS JPL (v2)|/v2/iris-jpl" - "accelerometer|Executive Accelerometer|/executive/accelerometer" - "genset|Genset Monitoring|/genset" + "kai-accelerometer|Executive Accelerometer|/executive/accelerometer" + "kai-genset|Genset Monitoring|/genset" "managerial|Managerial / Asset View|/managerial" "fatigue|Fatigue Monitoring|/fatigue" "sigap-tracker|SIGAP Tracker|/sigap-tracker" "sigap-dashboard|SIGAP Dashboard|/sigap-dashboard" - "railway-cargo-system|Railway Cargo System (RCS)|/railway-cargo-system" + "kai-rcs|Railway Cargo System (RCS)|/railway-cargo-system" ) -# Helper: Resolve Base URL -get_base_url() { - local env_name="$1" - case "$env_name" in - live) - echo "https://live.osmap.id" - ;; - beta) - echo "https://beta.osmap.id" - ;; - staging) - echo "https://staging.osmap.id" - ;; - demo) - echo "https://demo.osmap.id" - ;; - local) - echo "http://localhost:8000" - ;; - http://*|https://*) - echo "$env_name" - ;; - *) - echo "https://${env_name}" - ;; - esac -} - -# Helper: Copy to Clipboard +# ------------------------------------------------------------------------------ +# Common Utilities +# ------------------------------------------------------------------------------ copy_to_clipboard() { local text="$1" if command -v pbcopy >/dev/null 2>&1; then echo -n "$text" | pbcopy return 0 - elif command -v clip.exe >/dev/null 2>&1; then - printf "%s" "$text" | clip.exe - return 0 - elif command -v clip >/dev/null 2>&1; then - printf "%s" "$text" | clip - return 0 - elif command -v wl-copy >/dev/null 2>&1; then - echo -n "$text" | wl-copy - return 0 elif command -v xclip >/dev/null 2>&1; then echo -n "$text" | xclip -selection clipboard return 0 elif command -v xsel >/dev/null 2>&1; then echo -n "$text" | xsel --clipboard --input return 0 - elif command -v powershell.exe >/dev/null 2>&1; then - printf "%s" "$text" | powershell.exe -NoProfile -Command "Set-Clipboard" >/dev/null 2>&1 - return 0 fi return 1 } -# Helper: Open URL in Browser open_url() { local url="$1" if command -v open >/dev/null 2>&1; then @@ -107,41 +93,10 @@ open_url() { elif command -v start >/dev/null 2>&1; then start "$url" else - echo -e "${YELLOW}Could not detect browser launcher. Please open the URL manually.${NC}" + echo -e "${YELLOW}Could not detect browser launcher. URL:${NC} $url" fi } -# Helper: Load Credentials -load_credentials() { - # 1. Check CLI args / Env vars - APP_ID="${APP_ID:-$OSMAP_APP_ID}" - APP_SECRET="${APP_SECRET:-$OSMAP_APP_SECRET}" - - # 2. Check local config file - if [[ -z "$APP_ID" || -z "$APP_SECRET" ]] && [[ -f "$CONFIG_FILE" ]]; then - # shellcheck disable=SC1090 - source "$CONFIG_FILE" - APP_ID="${APP_ID:-$OSMAP_APP_ID}" - APP_SECRET="${APP_SECRET:-$OSMAP_APP_SECRET}" - fi - - # 3. Check .env in current directory - if [[ -z "$APP_ID" || -z "$APP_SECRET" ]] && [[ -f ".env" ]]; then - local env_app_id - local env_app_secret - env_app_id=$(grep -E "^OSMAP_APP_ID=" .env | cut -d '=' -f2- | tr -d '"' | tr -d "'" || true) - env_app_secret=$(grep -E "^OSMAP_APP_SECRET=" .env | cut -d '=' -f2- | tr -d '"' | tr -d "'" || true) - - APP_ID="${APP_ID:-$env_app_id}" - APP_SECRET="${APP_SECRET:-$env_app_secret}" - fi - - # 4. Fallback to built-in default credentials - APP_ID="${APP_ID:-$DEFAULT_APP_ID}" - APP_SECRET="${APP_SECRET:-$DEFAULT_APP_SECRET}" -} - -# Helper: Safe Read from TTY when piped (e.g. curl ... | bash) read_input() { if [[ -t 0 ]]; then read "$@" @@ -152,297 +107,461 @@ read_input() { fi } -# Helper: Prompt Credentials if missing -ensure_credentials() { - load_credentials - if [[ -z "$APP_ID" ]]; then - echo -e "${CYAN}Please enter your OSMAP App ID:${NC}" - read_input -r -p "App ID: " APP_ID - fi - if [[ -z "$APP_SECRET" ]]; then - echo -e "${CYAN}Please enter your OSMAP App Secret:${NC}" - read_input -r -s -p "App Secret: " APP_SECRET - echo "" - fi - - if [[ -z "$APP_ID" || -z "$APP_SECRET" ]]; then - echo -e "${RED}Error: App ID and App Secret are required.${NC}" >&2 - exit 1 - fi +# ------------------------------------------------------------------------------ +# KAI Token Helpers (live / beta / local) +# ------------------------------------------------------------------------------ +get_kai_base_url() { + local env="$1" + case "$env" in + live) echo "https://live.osmap.id" ;; + beta) echo "https://beta.osmap.id" ;; + local) echo "http://localhost:8000" ;; + demo) echo "https://demo.osmap.id" ;; + http*://*) echo "$env" ;; + *) echo "https://${env}" ;; + esac } -# Helper: Save credentials to config -save_credentials() { - echo "OSMAP_APP_ID=\"$APP_ID\"" > "$CONFIG_FILE" - echo "OSMAP_APP_SECRET=\"$APP_SECRET\"" >> "$CONFIG_FILE" - chmod 600 "$CONFIG_FILE" - echo -e "${GREEN}Credentials saved to ${CONFIG_FILE}${NC}" +get_kai_cache_file() { + local base_url="$1" + local sanitized + sanitized=$(echo "$base_url" | tr -d ':/' | tr '.' '_') + echo "${CACHE_DIR}/kai_token_${sanitized}.json" } -# Cache management -get_cache_file() { - local env_sanitized - env_sanitized=$(echo "$BASE_URL" | sed 's/[^a-zA-Z0-9]/_/g') - echo "${CACHE_DIR}/token_${env_sanitized}.json" -} - -# Step 3 per doc: Check token if valid -validate_token() { - local token="$1" - local endpoint="${BASE_URL}/api/apps/validate-token" - - local response - response=$(curl -s --location "$endpoint" \ - --header 'Content-Type: application/json' \ - --data "{\"token\":\"$token\"}" 2>/dev/null || echo '{"success":false}') - - local is_valid - is_valid=$(echo "$response" | jq -r '.success // false' 2>/dev/null || echo "false") - - if [[ "$is_valid" == "true" ]]; then - return 0 - else - return 1 - fi -} - -# Step 2 per doc: Get token through app_id and app_secret -fetch_new_token() { - ensure_credentials - local endpoint="${BASE_URL}/api/apps/token" - - echo -e "${CYAN}Requesting new embed token from ${endpoint}...${NC}" >&2 - - local payload - payload=$(jq -n \ - --arg app_id "$APP_ID" \ - --arg app_secret "$APP_SECRET" \ - '{app_id: $app_id, app_secret: $app_secret}') - - local response - response=$(curl -s --location "$endpoint" \ - --header 'Content-Type: application/json' \ - --data "$payload") - - local is_success - is_success=$(echo "$response" | jq -r '.success // false' 2>/dev/null || echo "false") - - if [[ "$is_success" != "true" ]]; then - local error_msg - error_msg=$(echo "$response" | jq -r '.message // "Failed to obtain token"' 2>/dev/null || echo "$response") - echo -e "${RED}Authentication Error:${NC} $error_msg" >&2 - exit 1 - fi - - local access_token - access_token=$(echo "$response" | jq -r '.access_token') - - # Save to cache +get_valid_kai_token() { + local base_url="$1" + local app_id="$2" + local app_secret="$3" local cache_file - cache_file=$(get_cache_file) - echo "$response" > "$cache_file" - chmod 600 "$cache_file" - - echo "$access_token" -} - -# Step 1-5 Flow: Get valid token (cached or fresh) -get_valid_token() { - local cache_file - cache_file=$(get_cache_file) - local token="" + cache_file=$(get_kai_cache_file "$base_url") if [[ -f "$cache_file" ]]; then + local token expires_at token=$(jq -r '.access_token // empty' "$cache_file" 2>/dev/null || true) - fi + expires_at=$(jq -r '.expires_at // empty' "$cache_file" 2>/dev/null || true) - if [[ -n "$token" ]]; then - # Validate existing token - if validate_token "$token"; then - echo "$token" - return 0 + if [[ -n "$token" && -n "$expires_at" ]]; then + local expiry_epoch=0 + if date -j -f "%Y-%m-%dT%H:%M:%S" "${expires_at:0:19}" +%s >/dev/null 2>&1; then + expiry_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%S" "${expires_at:0:19}" +%s) + elif date -d "$expires_at" +%s >/dev/null 2>&1; then + expiry_epoch=$(date -d "$expires_at" +%s) + fi + + local now_epoch + now_epoch=$(date +%s) + if [[ $expiry_epoch -gt $((now_epoch + 60)) ]]; then + echo "$token" + return 0 + fi fi fi - # Token missing or invalid -> fetch fresh token - fetch_new_token + # Fetch new KAI token + local endpoint="${base_url}/api/apps/token" + local payload + payload=$(jq -n --arg aid "$app_id" --arg asec "$app_secret" '{app_id: $aid, app_secret: $asec, expires_in_hours: 24}') + + local response + response=$(curl -s --location "$endpoint" -H "Content-Type: application/json" -d "$payload") + + local token + token=$(echo "$response" | jq -r '.access_token // empty' 2>/dev/null || true) + if [[ -z "$token" ]]; then + local err_msg + err_msg=$(echo "$response" | jq -r '.message // "Failed to obtain KAI token"' 2>/dev/null || echo "$response") + echo -e "${RED}Error fetching KAI token from ${base_url}:${NC} $err_msg" >&2 + return 1 + fi + + echo "$response" > "$cache_file" + chmod 600 "$cache_file" + echo "$token" } -# Revoke Tokens -revoke_tokens() { - ensure_credentials - local endpoint="${BASE_URL}/api/apps/revoke-tokens" - +# ------------------------------------------------------------------------------ +# Share Map Helpers (app.osmap.id) +# ------------------------------------------------------------------------------ +get_share_cache_file() { + local map_id="$1" + echo "${CACHE_DIR}/map_${map_id}.json" +} + +generate_share_token() { + local map_id="$1" + local app_id="$2" + local app_secret="$3" + local expires_in="${4:-$SHARE_MAP_EXPIRES_IN}" + local force_fresh="${5:-false}" + + if [[ "$force_fresh" != "true" ]]; then + local cache_file + cache_file=$(get_share_cache_file "$map_id") + if [[ -f "$cache_file" ]]; then + local token expires_at + token=$(jq -r '.token // empty' "$cache_file" 2>/dev/null || true) + expires_at=$(jq -r '.expires_at // empty' "$cache_file" 2>/dev/null || true) + + if [[ -n "$token" && -n "$expires_at" ]]; then + local expiry_epoch=0 + if date -j -f "%Y-%m-%dT%H:%M:%S" "${expires_at:0:19}" +%s >/dev/null 2>&1; then + expiry_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%S" "${expires_at:0:19}" +%s) + elif date -d "$expires_at" +%s >/dev/null 2>&1; then + expiry_epoch=$(date -d "$expires_at" +%s) + fi + local now_epoch + now_epoch=$(date +%s) + if [[ $expiry_epoch -gt $((now_epoch + 60)) ]]; then + echo "$token" + return 0 + fi + fi + fi + fi + + local endpoint="${APP_OSMAP_API}/apps/token/share-map" local payload payload=$(jq -n \ - --arg app_id "$APP_ID" \ - --arg app_secret "$APP_SECRET" \ - '{app_id: $app_id, app_secret: $app_secret}') + --arg aid "$app_id" \ + --arg asec "$app_secret" \ + --argjson exp "$expires_in" \ + --argjson mid "$map_id" \ + '{app_id: $aid, app_secret: $asec, expires_in: $exp, map_id: $mid}') - echo -e "${CYAN}Revoking tokens at ${endpoint}...${NC}" local response - response=$(curl -s --location "$endpoint" \ - --header 'Content-Type: application/json' \ - --data "$payload") + response=$(curl -s -X POST "$endpoint" -H "Content-Type: application/json" -d "$payload") + + local is_success token + is_success=$(echo "$response" | jq -r '.status // .success // false' 2>/dev/null || echo "false") + token=$(echo "$response" | jq -r '.data.token // .token // empty' 2>/dev/null || true) + + # Automatic fallback if first attempt lacked permissions + if [[ ("$is_success" != "true" || -z "$token") && "$app_id" != "$SHARE_FALLBACK_APP_ID" ]]; then + local error_code + error_code=$(echo "$response" | jq -r '.code // empty' 2>/dev/null || true) + if [[ "$error_code" == "SHARE_MAP_NO_PERMISSION" || "$error_code" == "UNAUTHORIZED" ]]; then + payload=$(jq -n \ + --arg aid "$SHARE_FALLBACK_APP_ID" \ + --arg asec "$SHARE_FALLBACK_APP_SECRET" \ + --argjson exp "$expires_in" \ + --argjson mid "$map_id" \ + '{app_id: $aid, app_secret: $asec, expires_in: $exp, map_id: $mid}') + + response=$(curl -s -X POST "$endpoint" -H "Content-Type: application/json" -d "$payload") + is_success=$(echo "$response" | jq -r '.status // .success // false' 2>/dev/null || echo "false") + token=$(echo "$response" | jq -r '.data.token // .token // empty' 2>/dev/null || true) + fi + fi + + if [[ "$is_success" != "true" || -z "$token" ]]; then + local error_msg + error_msg=$(echo "$response" | jq -r '.message // "Failed to generate share-map token"' 2>/dev/null || echo "$response") + echo -e "${RED}Error generating share token for Map ${map_id}:${NC} $error_msg" >&2 + return 1 + fi - echo "$response" | jq . - - # Clear local cache local cache_file - cache_file=$(get_cache_file) - rm -f "$cache_file" - echo -e "${GREEN}Local cache cleared.${NC}" + cache_file=$(get_share_cache_file "$map_id") + local expires_at + expires_at=$(echo "$response" | jq -r '.data.expires_at // empty' 2>/dev/null || true) + jq -n \ + --arg token "$token" \ + --argjson map_id "$map_id" \ + --arg expires_at "$expires_at" \ + '{token: $token, map_id: $map_id, expires_at: $expires_at}' > "$cache_file" + chmod 600 "$cache_file" + + echo "$token" } -# Register App -register_app() { - local endpoint="${BASE_URL}/api/apps/register" - echo -e "${BOLD}${CYAN}=== Register New App ===${NC}" - read -r -p "App Name: " app_name - read -r -p "Allowed Domains (comma-separated, e.g. demo.osmap.id,localhost): " allowed_domains_raw +# ------------------------------------------------------------------------------ +# Display Functions +# ------------------------------------------------------------------------------ +display_share_map() { + local key="$1" + local map_id="$2" + local name="$3" + local app_id="$4" + local app_secret="$5" + local desc="$6" + local auto_open="$7" + local auto_copy="$8" + local show_iframe="$9" - # Convert comma-separated string to JSON array - local domains_json - domains_json=$(echo "$allowed_domains_raw" | jq -R 'split(",") | map(gsub("^[ \t]+|[ \t]+$"; ""))') - - local payload - payload=$(jq -n \ - --arg app_name "$app_name" \ - --argjson domains "$domains_json" \ - '{app_name: $app_name, allowed_domains: $domains}') - - echo -e "${CYAN}Sending registration request to ${endpoint}...${NC}" - local response - response=$(curl -s --location "$endpoint" \ - --header 'Content-Type: application/json' \ - --data "$payload") - - echo "$response" | jq . - - local success - success=$(echo "$response" | jq -r '.success // false' 2>/dev/null || echo "false") - if [[ "$success" == "true" ]]; then - local new_app_id - local new_app_secret - new_app_id=$(echo "$response" | jq -r '.app_id // empty') - new_app_secret=$(echo "$response" | jq -r '.app_secret // empty') - - echo "" - read -r -p "Do you want to save these credentials to ${CONFIG_FILE}? (y/N): " save_confirm - if [[ "$save_confirm" =~ ^[Yy]$ ]]; then - APP_ID="$new_app_id" - APP_SECRET="$new_app_secret" - save_credentials - fi + echo -e "${CYAN}Generating share token for ${BOLD}${name}${NC}${CYAN} (Map ID: ${map_id})...${NC}" + local token + if ! token=$(generate_share_token "$map_id" "$app_id" "$app_secret" "$SHARE_MAP_EXPIRES_IN" "false"); then + return 1 fi -} -# Print single URL -display_and_handle_url() { - local name="$1" - local path="$2" - local token="$3" - local auto_open="$4" - - local full_url="${BASE_URL}${path}?token=${token}" + local embed_url="https://${APP_OSMAP_DOMAIN}/maps/embed/${map_id}?token=${token}" + local iframe_code="" echo "" - echo -e "${BOLD}${GREEN}Map:${NC} ${name}" - echo -e "${BOLD}${CYAN}URL:${NC} ${full_url}" + echo -e "${BOLD}${GREEN}✔ ${name}${NC} (Map ID: ${BOLD}${map_id}${NC})" + if [[ -n "$desc" ]]; then + echo -e " ${YELLOW}Description:${NC} ${desc}" + fi + echo -e " ${BOLD}${CYAN}Embed URL:${NC} ${embed_url}" + echo -e " ${BOLD}${MAGENTA}Token:${NC} ${token}" + + if [[ "$show_iframe" == "true" ]]; then + echo -e " ${BOLD}${YELLOW}Iframe Code:${NC}" + echo " ${iframe_code}" + fi echo "" - if copy_to_clipboard "$full_url"; then - echo -e "${GREEN}✓ Copied URL to clipboard!${NC}" + if [[ "$auto_copy" == "true" ]]; then + if copy_to_clipboard "$embed_url"; then + echo -e " ${GREEN}✓ Copied Embed URL to clipboard!${NC}" + fi fi if [[ "$auto_open" == "true" ]]; then - echo -e "${CYAN}Opening in browser...${NC}" + echo -e " ${CYAN}Opening in browser...${NC}" + open_url "$embed_url" + fi +} + +display_kai_map() { + local name="$1" + local path="$2" + local base_url="$3" + local app_id="$4" + local app_secret="$5" + local auto_open="$6" + local auto_copy="$7" + + echo -e "${CYAN}Retrieving token for ${base_url}...${NC}" + local token + if ! token=$(get_valid_kai_token "$base_url" "$app_id" "$app_secret"); then + return 1 + fi + + local full_url="${base_url}${path}?token=${token}" + + echo "" + echo -e "${BOLD}${GREEN}✔ ${name}${NC} (${base_url})" + echo -e " ${BOLD}${CYAN}Embed URL:${NC} ${full_url}" + echo -e " ${BOLD}${MAGENTA}Token:${NC} ${token}" + echo "" + + if [[ "$auto_copy" == "true" ]]; then + if copy_to_clipboard "$full_url"; then + echo -e " ${GREEN}✓ Copied URL to clipboard!${NC}" + fi + fi + + if [[ "$auto_open" == "true" ]]; then + echo -e " ${CYAN}Opening in browser...${NC}" open_url "$full_url" fi } -# Print all URLs -print_all_urls() { - local token="$1" +print_all_curls() { echo "" - echo -e "${BOLD}${CYAN}=== OSMAP All Embed URLs (${TARGET_ENV}) ===${NC}" + echo -e "${BOLD}${CYAN}========================================================================================${NC}" + echo -e "${BOLD}${CYAN} OSMAP cURL Reference (All Domains) ${NC}" + echo -e "${BOLD}${CYAN}========================================================================================${NC}" echo "" - printf "%-30s | %s\n" "Map Feature" "Complete Embed URL" - printf "%-30s-+-%s\n" "------------------------------" "------------------------------------------------------------" - for item in "${ROUTES[@]}"; do - IFS="|" read -r key name path <<< "$item" - local url="${BASE_URL}${path}?token=${token}" - printf "%-30s | %s\n" "$name" "$url" + echo -e "${BOLD}${YELLOW}--- 1. NEW SHARE-MAP SYSTEM (Domain: app.osmap.id) ---${NC}" + echo "" + local idx=1 + for preset in "${SHARE_MAP_PRESETS[@]}"; do + IFS="|" read -r key map_id name app_id app_secret desc <<< "$preset" + echo -e "${BOLD}${GREEN}${idx}. ${name}${NC} (Map ID: ${map_id})" + cat << EOF +curl -X POST "https://api.osmap.id/apps/token/share-map" \\ + -H "Content-Type: application/json" \\ + -d '{"app_id":"${app_id}","app_secret":"${app_secret}","expires_in":86400,"map_id":${map_id}}' +EOF + echo "" + ((idx++)) + done + + echo -e "${BOLD}${YELLOW}--- 2. OSMAP KAI SYSTEM (Domain: live.osmap.id) ---${NC}" + echo "" + cat << EOF +curl -X POST "https://live.osmap.id/api/apps/token" \\ + -H "Content-Type: application/json" \\ + -d '{"app_id":"${DEFAULT_KAI_APP_ID}","app_secret":"${DEFAULT_KAI_APP_SECRET}","expires_in_hours":24}' +EOF + echo "" + + echo -e "${BOLD}${YELLOW}--- 3. OSMAP KAI SYSTEM (Domain: beta.osmap.id) ---${NC}" + echo "" + cat << EOF +curl -X POST "https://beta.osmap.id/api/apps/token" \\ + -H "Content-Type: application/json" \\ + -d '{"app_id":"${DEFAULT_KAI_APP_ID}","app_secret":"${DEFAULT_KAI_APP_SECRET}","expires_in_hours":24}' +EOF + echo "" +} + +print_all_share_table() { + echo "" + echo -e "${BOLD}${CYAN}========================================================================================${NC}" + echo -e "${BOLD}${CYAN} OSMAP Share Map Embed URLs (Domain: ${APP_OSMAP_DOMAIN}) ${NC}" + echo -e "${BOLD}${CYAN}========================================================================================${NC}" + echo "" + printf "%-5s | %-20s | %s\n" "ID" "Map Name" "Embed URL with Token" + printf "%-5s-+-%-20s-+-%s\n" "-----" "--------------------" "----------------------------------------------------------------------------------" + + for preset in "${SHARE_MAP_PRESETS[@]}"; do + IFS="|" read -r key map_id name app_id app_secret desc <<< "$preset" + local token + token=$(generate_share_token "$map_id" "$app_id" "$app_secret" "$SHARE_MAP_EXPIRES_IN" "false" 2>/dev/null || echo "") + if [[ -n "$token" ]]; then + printf "%-5s | %-20s | %s\n" "$map_id" "$name" "https://${APP_OSMAP_DOMAIN}/maps/embed/${map_id}?token=${token}" + else + printf "%-5s | %-20s | %s\n" "$map_id" "$name" "${RED}[Failed to generate token]${NC}" + fi done echo "" } +print_all_kai_table() { + local env="${1:-live}" + local base_url + base_url=$(get_kai_base_url "$env") + echo "" + echo -e "${BOLD}${CYAN}========================================================================================${NC}" + echo -e "${BOLD}${CYAN} OSMAP KAI Embed URLs (Domain / Base URL: ${base_url}) ${NC}" + echo -e "${BOLD}${CYAN}========================================================================================${NC}" + echo "" + local token + token=$(get_valid_kai_token "$base_url" "$DEFAULT_KAI_APP_ID" "$DEFAULT_KAI_APP_SECRET" 2>/dev/null || echo "") + if [[ -z "$token" ]]; then + echo -e "${RED}Failed to obtain valid token for ${base_url}${NC}" + return 1 + fi + + printf "%-30s | %s\n" "Map Feature" "Complete Embed URL" + printf "%-30s-+-%s\n" "------------------------------" "------------------------------------------------------------" + for item in "${KAI_ROUTES[@]}"; do + IFS="|" read -r key name path <<< "$item" + printf "%-30s | %s\n" "$name" "${base_url}${path}?token=${token}" + done + echo "" +} + +# ------------------------------------------------------------------------------ # Interactive Menu +# ------------------------------------------------------------------------------ show_interactive_menu() { - local token="$1" + local current_kai_env="$DEFAULT_KAI_ENV" while true; do + local kai_base + kai_base=$(get_kai_base_url "$current_kai_env") + echo "" - echo -e "${BOLD}${CYAN}╔════════════════════════════════════════════════════════════════╗${NC}" - echo -e "${BOLD}${CYAN}║ OSMAP KAI Embed Map Navigator ║${NC}" - echo -e "${BOLD}${CYAN}╠════════════════════════════════════════════════════════════════╣${NC}" - echo -e "${CYAN}║ Target Environment:${NC} ${BOLD}${YELLOW}${BASE_URL}${NC}" - echo -e "${BOLD}${CYAN}╚════════════════════════════════════════════════════════════════╝${NC}" + echo -e "${BOLD}${CYAN}╔══════════════════════════════════════════════════════════════════════════╗${NC}" + echo -e "${BOLD}${CYAN}║ OSMAP Universal Embed Map Navigator ║${NC}" + echo -e "${BOLD}${CYAN}╠══════════════════════════════════════════════════════════════════════════╣${NC}" + echo -e "${CYAN}║ 1. Share-Map Domain: ${BOLD}${YELLOW}${APP_OSMAP_DOMAIN}${NC}${CYAN} (API: api.osmap.id/apps/token/share-map)${NC}" + echo -e "${CYAN}║ 2. KAI Active Domain: ${BOLD}${YELLOW}${kai_base}${NC}${CYAN} (live.osmap.id / beta.osmap.id)${NC}" + echo -e "${BOLD}${CYAN}╚══════════════════════════════════════════════════════════════════════════╝${NC}" echo "" - local index=1 - for item in "${ROUTES[@]}"; do - IFS="|" read -r key name path <<< "$item" - printf " ${BOLD}%2d)${NC} %-32s ${CYAN}%s${NC}\n" "$index" "$name" "$path" - ((index++)) + echo -e "${BOLD}${YELLOW}─── [A] SHARE-MAP PRESETS (app.osmap.id) ───${NC}" + local idx=1 + for preset in "${SHARE_MAP_PRESETS[@]}"; do + IFS="|" read -r key map_id name app_id app_secret desc <<< "$preset" + printf " ${BOLD}%2d)${NC} %-22s ${CYAN}(Map ID: %-2s)${NC} - %s\n" "$idx" "$name" "$map_id" "$desc" + ((idx++)) done echo "" - printf " ${BOLD}%2d)${NC} %s\n" "$index" "Display All URLs Table" - ((index++)) - printf " ${BOLD}%2d)${NC} %s\n" "$index" "Copy Raw Token" - ((index++)) - printf " ${BOLD}%2d)${NC} %s\n" "$index" "Switch Environment (live / beta / local / demo / custom)" - ((index++)) + echo -e "${BOLD}${YELLOW}─── [B] OSMAP KAI ROUTES (${current_kai_env}: ${kai_base}) ───${NC}" + local kai_start=$idx + for item in "${KAI_ROUTES[@]}"; do + IFS="|" read -r key name path <<< "$item" + printf " ${BOLD}%2d)${NC} %-25s ${CYAN}%s${NC}\n" "$idx" "$name" "$path" + ((idx++)) + done + + echo "" + echo -e "${BOLD}${YELLOW}─── [C] UTILITIES & SWITCH DOMAIN ───${NC}" + printf " ${BOLD}%2d)${NC} %s\n" "$idx" "Display All app.osmap.id Embed URLs Table" + local opt_table_share=$idx + ((idx++)) + printf " ${BOLD}%2d)${NC} %s\n" "$idx" "Display All KAI Embed URLs Table (${current_kai_env})" + local opt_table_kai=$idx + ((idx++)) + printf " ${BOLD}%2d)${NC} %s\n" "$idx" "Print All cURL Commands (app.osmap.id, live.osmap.id, beta.osmap.id)" + local opt_curls=$idx + ((idx++)) + printf " ${BOLD}%2d)${NC} %s\n" "$idx" "Switch KAI Environment (live / beta / local / custom URL)" + local opt_switch_env=$idx + ((idx++)) + printf " ${BOLD}%2d)${NC} %s\n" "$idx" "Custom Map ID (app.osmap.id)" + local opt_custom_mid=$idx + ((idx++)) printf " ${BOLD} 0)${NC} %s\n" "Exit" echo "" - read_input -r -p "Select an option [0-$((index-1))]: " choice + read_input -r -p "Select an option [0-$((idx-1))]: " choice if [[ "$choice" == "0" || "$choice" == "q" || "$choice" == "exit" ]]; then echo -e "${GREEN}Goodbye!${NC}" exit 0 fi - if [[ "$choice" -ge 1 && "$choice" -le ${#ROUTES[@]} ]]; then - local selected_item="${ROUTES[$((choice-1))]}" - IFS="|" read -r key name path <<< "$selected_item" - - display_and_handle_url "$name" "$path" "$token" "false" - - read_input -r -p "Open this URL in your default browser? (Y/n): " open_choice - if [[ "$open_choice" =~ ^[Nn]$ ]]; then - : - else - open_url "${BASE_URL}${path}?token=${token}" - fi - elif [[ "$choice" -eq $(( ${#ROUTES[@]} + 1 )) ]]; then - print_all_urls "$token" - elif [[ "$choice" -eq $(( ${#ROUTES[@]} + 2 )) ]]; then - if copy_to_clipboard "$token"; then - echo -e "${GREEN}✓ Access Token copied to clipboard:${NC} $token" - else - echo -e "${YELLOW}Could not detect clipboard tool. Here is the token:${NC}" - echo "$token" - fi - elif [[ "$choice" -eq $(( ${#ROUTES[@]} + 3 )) ]]; then + # Option: Share Map Presets (1..5) + if [[ "$choice" -ge 1 && "$choice" -le ${#SHARE_MAP_PRESETS[@]} ]]; then + local selected="${SHARE_MAP_PRESETS[$((choice-1))]}" + IFS="|" read -r key map_id name app_id app_secret desc <<< "$selected" + display_share_map "$key" "$map_id" "$name" "$app_id" "$app_secret" "$desc" "false" "true" "true" + echo "" - read_input -r -p "Enter environment [live, beta, staging, demo, local, or URL]: " new_env + read_input -r -p "Open this map in default browser? (Y/n): " open_choice + if [[ ! "$open_choice" =~ ^[Nn]$ ]]; then + local token + token=$(generate_share_token "$map_id" "$app_id" "$app_secret" "$SHARE_MAP_EXPIRES_IN" "false") + open_url "https://${APP_OSMAP_DOMAIN}/maps/embed/${map_id}?token=${token}" + fi + + # Option: KAI Routes + elif [[ "$choice" -ge "$kai_start" && "$choice" -lt "$opt_table_share" ]]; then + local kai_idx=$(( choice - kai_start )) + local selected_kai="${KAI_ROUTES[$kai_idx]}" + IFS="|" read -r key name path <<< "$selected_kai" + display_kai_map "$name" "$path" "$kai_base" "$DEFAULT_KAI_APP_ID" "$DEFAULT_KAI_APP_SECRET" "false" "true" + + echo "" + read_input -r -p "Open this route in default browser? (Y/n): " open_choice + if [[ ! "$open_choice" =~ ^[Nn]$ ]]; then + local token + token=$(get_valid_kai_token "$kai_base" "$DEFAULT_KAI_APP_ID" "$DEFAULT_KAI_APP_SECRET") + open_url "${kai_base}${path}?token=${token}" + fi + + elif [[ "$choice" -eq "$opt_table_share" ]]; then + print_all_share_table + elif [[ "$choice" -eq "$opt_table_kai" ]]; then + print_all_kai_table "$current_kai_env" + elif [[ "$choice" -eq "$opt_curls" ]]; then + print_all_curls + elif [[ "$choice" -eq "$opt_switch_env" ]]; then + echo "" + read_input -r -p "Enter environment [live, beta, local, demo, or custom URL]: " new_env if [[ -n "$new_env" ]]; then - TARGET_ENV="$new_env" - BASE_URL=$(get_base_url "$TARGET_ENV") - echo -e "${CYAN}Validating token for ${BASE_URL}...${NC}" - token=$(get_valid_token) + current_kai_env="$new_env" + echo -e "${GREEN}Switched KAI domain to:${NC} $(get_kai_base_url "$current_kai_env")" + fi + elif [[ "$choice" -eq "$opt_custom_mid" ]]; then + echo "" + read_input -r -p "Enter Map ID: " custom_mid + read_input -r -p "Enter App ID [Press Enter for default]: " custom_aid + read_input -r -p "Enter App Secret [Press Enter for default]: " custom_asec + + custom_aid="${custom_aid:-$SHARE_FALLBACK_APP_ID}" + custom_asec="${custom_asec:-$SHARE_FALLBACK_APP_SECRET}" + + if [[ -n "$custom_mid" ]]; then + display_share_map "custom" "$custom_mid" "Custom Map ${custom_mid}" "$custom_aid" "$custom_asec" "Custom Map ID" "false" "true" "true" fi else echo -e "${RED}Invalid selection.${NC}" @@ -450,75 +569,86 @@ show_interactive_menu() { done } +# ------------------------------------------------------------------------------ # Help / Usage +# ------------------------------------------------------------------------------ show_help() { cat << EOF -OSMAP Embed CLI Tool +OSMAP Embed Map & Token CLI Tool (Multi-Domain) + +Supported Domains & APIs: + 1. app.osmap.id -> Share-Map API (https://api.osmap.id/apps/token/share-map) + 2. live.osmap.id -> KAI App Token (https://live.osmap.id/api/apps/token) + 3. beta.osmap.id -> KAI App Token (https://beta.osmap.id/api/apps/token) Usage: - ./osmap-embed.sh [route|command] [options] + ./osmap-embed.sh [command|map|route] [options] -Commands & Short-names: - interactive Launch interactive selection menu (default) - all Display all embed URLs with token - token Print current valid token only - validate Check if cached token is still valid - revoke Revoke current app tokens - register Register a new application - -Routes: +Commands: + interactive Launch interactive navigator menu (default) + curls Display all cURL commands for app.osmap.id, live.osmap.id, beta.osmap.id + all Display embed URLs table for app.osmap.id (or with --env live/beta for KAI) + token Generate and print only the token string + validate Validate KAI token + revoke Revoke KAI app token + +Share Map Presets (app.osmap.id): + accelerometer | 94 Map Accelerometer (Map ID: 94) + rocc | 46 Map ROCC (Map ID: 46) + genset | 74 Map Genset (Map ID: 74) + rcs | 72 Map RCS (Map ID: 72) + jpl | 70 Map JPL (Map ID: 70) + +KAI Routes (live.osmap.id / beta.osmap.id): executive /executive rolling /rolling iris-jpl /iris-jpl iris-jpl-v2 /v2/iris-jpl - accelerometer /executive/accelerometer - genset /genset + kai-accelerometer /executive/accelerometer + kai-genset /genset managerial /managerial fatigue /fatigue sigap-tracker /sigap-tracker sigap-dashboard /sigap-dashboard - railway-cargo-system /railway-cargo-system + kai-rcs /railway-cargo-system Options: - -e, --env Environment: live (default), beta, staging, demo, local, or custom URL - --app-id Override App ID - --app-secret Override App Secret - -o, --open Automatically open the generated URL in browser + -e, --env Environment for KAI: live (default), beta, local, or URL + --domain Override Share-Map domain (default: ${APP_OSMAP_DOMAIN}) + --map-id Specify custom Map ID for app.osmap.id + -o, --open Automatically open URL in default browser -c, --copy Copy URL to clipboard - --save Save provided App ID / Secret to .osmap_credentials + --iframe Show