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
70 changes: 70 additions & 0 deletions agent-fight/copilot/.github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copilot Instructions for api-blaster-copilot

## Repository Overview

**Purpose**: Python utility that automates OAuth2 authentication for generic API clients (originally developed for Amazon's ASP API).
**Structure**: Single-file script (~128 lines) with no build system, tests, or CI/CD
**Language**: Python 3.6+ (tested with 3.12.3)
**Dependencies**: `requests`, `requests-oauthlib`

## Build and Validation Instructions

**ALWAYS install dependencies first**: `pip install requests requests-oauthlib`

**Validation Commands** (run in this order):
1. Syntax: `python3 -m py_compile api-blaster.py`
2. Dependencies: `python3 -c "import requests; from requests_oauthlib import OAuth2Session; print('Dependencies OK')"`
3. Test run: `timeout 3 python3 api-blaster.py 2>&1 || echo "Script execution ended"`
> **Note:** The `timeout` command is available on most Unix-like systems (Linux, macOS), but **not by default on Windows**.
> - On Windows, you can run the script manually: `python api-blaster.py` and terminate it after a few seconds if needed.
> - Alternatively, use PowerShell: `Start-Process python -ArgumentList 'api-blaster.py'; Start-Sleep -Seconds 3; Stop-Process -Name python`
> - Or install GNU utilities for Windows to get the `timeout` command.
**No Build System**: This repository has NO build tools, tests, CI/CD, linting configs, or package management files.

## Project Architecture and Layout

**File Structure**:
```
/
├── .gitignore # Standard Python gitignore
├── README.md # Basic setup instructions
├── api-blaster.py # Main script (ONLY source file)
└── .api-blaster/ # Created at runtime (in .gitignore)
├── api-blaster-conf.json # OAuth client config
└── api-blaster-auth.json # OAuth tokens
```

**Key Code Sections in api-blaster.py**:
- **Config Management** (lines 27-46): Loads/saves OAuth credentials, interactive setup
- **Token Management** (lines 51-97): Handles tokens and auto-refresh
- **User Code Area** (lines 100-128): Template for API automation (most changes go here)

**OAuth Details**:
- Authorization URL: `https://www.amazon.com/ap/oa`
- Token URL: `https://api.amazon.com/auth/O2/token`
- Scopes: `alexa::enterprise:management`, `credential_locker::wifi_management`, `profile`

## Critical Information

**Authentication State**: Script maintains OAuth state in `.api-blaster/` directory (already in .gitignore)
**NEVER commit `.api-blaster/` files** - they contain sensitive tokens

**Interactive Requirements**: Script requires user input for initial setup. Use `timeout` command for testing.

**Common Changes**: Most modifications go in the user customization section (after line 104). Follow this error handling pattern:
```python
try:
response.raise_for_status()
print(json.dumps(response.json(), indent=2))
except requests.HTTPError as e:
print(f"HTTP error: {e}")
print(f"Response content: {response.text}")
```

**Troubleshooting**: If authentication breaks, delete `.api-blaster/` directory to reset OAuth state.

**Validation**: After changes, always run syntax check: `python3 -m py_compile api-blaster.py`

## Trust These Instructions

These instructions are comprehensive and tested. Only search for additional information if these instructions are incomplete or fail unexpectedly. The repository structure is simple with only one source file (api-blaster.py) containing ~128 lines of OAuth automation code.
23 changes: 23 additions & 0 deletions agent-fight/copilot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# api-blaster

Script that gets oauth out of your way and lets you automate tasks against any OAuth2 API

## Deps & Execution

```bash
uv init
uv venv
source .venv/bin/activate
uv add requests requests-oauthlib
uv run api-blaster-example.py
```

## What it does

1. Asks you for your client id, client secret, and https redirect url and then saves the config to ./.api-blaster/api-blaster-conf.json
2. Walks you though logging in and getting an initial auth token and saves auth, refresh, etc to ./.api-blaster/api-blaster-auth.json
3. After that you're set. It will refresh tokens as necessary

## What you do

1. Fill in what you need to do at the bottom of the script. Everything is done with the popular python [requests](https://requests.readthedocs.io/en/latest/) library.
38 changes: 38 additions & 0 deletions agent-fight/copilot/api-blaster-example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""
Example script showing how to use the OAuth functionality from api-blaster.py
This file contains the specific API calling logic that was extracted from the original script.
"""

import requests
import importlib.util
import sys

# Import the OAuth functionality from api-blaster.py
spec = importlib.util.spec_from_file_location("api_blaster", "api-blaster.py")
api_blaster = importlib.util.module_from_spec(spec)
sys.modules["api_blaster"] = api_blaster
spec.loader.exec_module(api_blaster)

def main():
# Get authenticated session from api-blaster OAuth logic
api_session = api_blaster.get_authenticated_session()

# Example API call - this is the logic extracted from api-blaster.py
url = "https://api.justinsweb.com/prod/echo"
response = api_session.post(url, json={"message": "YOUR MESSAGE HERE"})
try:
response.raise_for_status()
print(response.text) # Response is a string, not JSON
except requests.HTTPError as e:
print(f"HTTP error: {e}")
print(f"Response content: {response.text}")

# Additional examples of how you could use the authenticated session:
# response = api_session.get('https://httpbin.org/get')
# response = api_session.post('https://httpbin.org/post', data={'key': 'value'})
# response = api_session.put('https://httpbin.org/put', data={'key': 'value'})
# response = api_session.delete('https://httpbin.org/delete')

if __name__ == "__main__":
main()
137 changes: 137 additions & 0 deletions agent-fight/copilot/api-blaster.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import requests
import json
import time
import pathlib
from requests_oauthlib import OAuth2Session

authorization_base_url = "https://cognito.justinsweb.com/oauth2/authorize"
token_url = "https://cognito.justinsweb.com/oauth2/token"
refresh_url = token_url
scope = [
"jw/admin",
]

auth_file_name = "./.api-blaster/api-blaster-auth.json"
conf_file_name = "./.api-blaster/api-blaster-conf.json"
pathlib.Path("./.api-blaster").mkdir(parents=True, exist_ok=True)


def token_updater(token):
with open(auth_file_name, "w") as f:
f.write(json.dumps(token))


def get_authenticated_session():
"""
Get an authenticated OAuth2Session for API calls.
Returns an OAuth2Session object ready for making API requests.
"""
# Load a saved config or prompt for values
try:
# Load config
with open(conf_file_name, "r") as f:
conf = json.load(f)

# Quick sanity check
conf["client_id"]
conf["client_secret"]
conf["redirect_uri"]
except (FileNotFoundError, json.JSONDecodeError, KeyError):
# Prompt for config
print("No config saved to file yet, lets get set up!")
conf = {}
conf["client_id"] = input("Enter your Client ID: ")
conf["client_secret"] = input("Enter your Client Secret: ")
redirect_uri_input = input("Enter your redirect URI (default: https://localhost:9090/cb): ").strip()
conf["redirect_uri"] = redirect_uri_input if redirect_uri_input else "https://localhost:9090/cb"

# Save config
with open(conf_file_name, "w") as f:
f.write(json.dumps(conf))

client_creds = {"client_id": conf["client_id"], "client_secret": conf["client_secret"]}

# Load a saved token or get a new one
try:
# Read the token file
with open(auth_file_name, "r") as f:
token = json.load(f)

# Quick sanity check
token["refresh_token"]
token["expires_at"]

# Load the client
api_blaster = OAuth2Session(
client_id=conf["client_id"],
token=token,
auto_refresh_kwargs=client_creds,
auto_refresh_url=refresh_url,
token_updater=token_updater,
)
except (FileNotFoundError, json.JSONDecodeError, KeyError):
print("No auth saved to file yet, lets get logged in!")

# Load the client
api_blaster = OAuth2Session(
client_id=conf["client_id"],
scope=scope,
redirect_uri=conf["redirect_uri"],
auto_refresh_kwargs=client_creds,
auto_refresh_url=refresh_url,
token_updater=token_updater,
)

# Give user the Amazon login url for authorization
authorization_url, state = api_blaster.authorization_url(authorization_base_url)
print("Please go here and authorize: ", authorization_url)

# Get the authorization verifier code from the callback url
redirect_response = input("Paste the full redirect URL here: ")

# Fetch the access token
token = api_blaster.fetch_token(
token_url,
client_secret=conf["client_secret"],
authorization_response=redirect_response,
)

# Save the token
token_updater(token)

return api_blaster


def example_api_call(api_session):
"""
Example function showing how to use the authenticated session.
This is the logic that was originally at the bottom of this script.
"""
url = "https://api.justinsweb.com/prod/echo"
response = api_session.post(url, json={"message": "YOUR MESSAGE HERE"})
try:
response.raise_for_status()
print(response.text) # Response is a string, not JSON
except requests.HTTPError as e:
print(f"HTTP error: {e}")
print(f"Response content: {response.text}")


if __name__ == "__main__":
# When run directly, execute the example API call
api_blaster = get_authenticated_session()
example_api_call(api_blaster)

################################
#
# Requests Library Docs and Examples...
#
# https://requests.readthedocs.io/en/latest/user/quickstart/
# response = api_session.get('https://httpbin.org/get')
# response = api_session.post('https://httpbin.org/post', data={'key': 'value'})
# response = api_session.put('https://httpbin.org/put', data={'key': 'value'})
# response = api_session.delete('https://httpbin.org/delete')
# response = api_session.head('https://httpbin.org/get')
# response = api_session.options('https://httpbin.org/get')
#
################################
10 changes: 10 additions & 0 deletions agent-fight/copilot/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[project]
name = "api-blaster-copilot"
version = "0.1.0"
description = "Script that gets oauth out of your way and lets you automate tasks against any OAuth2 API"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"requests>=2.32.5",
"requests-oauthlib>=2.0.0",
]
Loading