From ae45e9777b11b53ce0a54ef30b7d5fa16160566e Mon Sep 17 00:00:00 2001 From: Justin Pecott Date: Mon, 13 Oct 2025 18:12:11 -0700 Subject: [PATCH 1/2] Add Agent Fight results from last month --- .../copilot/.github/copilot-instructions.md | 70 +++++++ agent-fight/copilot/README.md | 23 +++ agent-fight/copilot/api-blaster-example.py | 38 ++++ agent-fight/copilot/api-blaster.py | 137 +++++++++++++ agent-fight/copilot/pyproject.toml | 10 + agent-fight/copilot/uv.lock | 113 +++++++++++ agent-fight/gpt/.gitignore | 165 +++++++++++++++ agent-fight/gpt/AGENTS.md | 19 ++ agent-fight/gpt/README.md | 21 ++ agent-fight/gpt/api-blaster-example.py | 28 +++ agent-fight/gpt/api-blaster.py | 106 ++++++++++ agent-fight/gpt/pyproject.toml | 10 + agent-fight/gpt/uv.lock | 113 +++++++++++ agent-fight/justinsweb-api.md | 9 + agent-fight/results.md | 5 + agent-fight/tasks.md | 188 ++++++++++++++++++ 16 files changed, 1055 insertions(+) create mode 100644 agent-fight/copilot/.github/copilot-instructions.md create mode 100644 agent-fight/copilot/README.md create mode 100644 agent-fight/copilot/api-blaster-example.py create mode 100644 agent-fight/copilot/api-blaster.py create mode 100644 agent-fight/copilot/pyproject.toml create mode 100644 agent-fight/copilot/uv.lock create mode 100644 agent-fight/gpt/.gitignore create mode 100644 agent-fight/gpt/AGENTS.md create mode 100644 agent-fight/gpt/README.md create mode 100644 agent-fight/gpt/api-blaster-example.py create mode 100644 agent-fight/gpt/api-blaster.py create mode 100644 agent-fight/gpt/pyproject.toml create mode 100644 agent-fight/gpt/uv.lock create mode 100644 agent-fight/justinsweb-api.md create mode 100644 agent-fight/results.md create mode 100644 agent-fight/tasks.md diff --git a/agent-fight/copilot/.github/copilot-instructions.md b/agent-fight/copilot/.github/copilot-instructions.md new file mode 100644 index 0000000..872ffb6 --- /dev/null +++ b/agent-fight/copilot/.github/copilot-instructions.md @@ -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. \ No newline at end of file diff --git a/agent-fight/copilot/README.md b/agent-fight/copilot/README.md new file mode 100644 index 0000000..b38f66e --- /dev/null +++ b/agent-fight/copilot/README.md @@ -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. diff --git a/agent-fight/copilot/api-blaster-example.py b/agent-fight/copilot/api-blaster-example.py new file mode 100644 index 0000000..00ccec8 --- /dev/null +++ b/agent-fight/copilot/api-blaster-example.py @@ -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() \ No newline at end of file diff --git a/agent-fight/copilot/api-blaster.py b/agent-fight/copilot/api-blaster.py new file mode 100644 index 0000000..1b026fd --- /dev/null +++ b/agent-fight/copilot/api-blaster.py @@ -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') +# +################################ diff --git a/agent-fight/copilot/pyproject.toml b/agent-fight/copilot/pyproject.toml new file mode 100644 index 0000000..d72a663 --- /dev/null +++ b/agent-fight/copilot/pyproject.toml @@ -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", +] diff --git a/agent-fight/copilot/uv.lock b/agent-fight/copilot/uv.lock new file mode 100644 index 0000000..2664148 --- /dev/null +++ b/agent-fight/copilot/uv.lock @@ -0,0 +1,113 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "api-blaster-copilot" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "requests" }, + { name = "requests-oauthlib" }, +] + +[package.metadata] +requires-dist = [ + { name = "requests", specifier = ">=2.32.5" }, + { name = "requests-oauthlib", specifier = ">=2.0.0" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "oauthlib" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] diff --git a/agent-fight/gpt/.gitignore b/agent-fight/gpt/.gitignore new file mode 100644 index 0000000..84550e1 --- /dev/null +++ b/agent-fight/gpt/.gitignore @@ -0,0 +1,165 @@ +# Local API client credentials +.api-blaster/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/agent-fight/gpt/AGENTS.md b/agent-fight/gpt/AGENTS.md new file mode 100644 index 0000000..410da5f --- /dev/null +++ b/agent-fight/gpt/AGENTS.md @@ -0,0 +1,19 @@ +# AGENTS Instructions + +These guidelines help automate contributions for this repository. + +## Scope + +Applies to the entire repository. + +## Code Style +- Format Python code with `black` (default settings). +- Target Python 3.8+ and adhere to PEP 8 where `black` allows. + +## Testing +- Run `python -m py_compile` on any Python files you modify to verify syntax. +- If tests are added in the future, run `pytest` before committing. + +## Commit Checklist +- Ensure `git status` is clean before finalizing your work. +- Include descriptive commit messages. diff --git a/agent-fight/gpt/README.md b/agent-fight/gpt/README.md new file mode 100644 index 0000000..945fe09 --- /dev/null +++ b/agent-fight/gpt/README.md @@ -0,0 +1,21 @@ +# api-blaster + +Script that gets OAuth out of your way and lets you automate tasks against any API + +## Deps & Execution + +```bash +uv venv +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. diff --git a/agent-fight/gpt/api-blaster-example.py b/agent-fight/gpt/api-blaster-example.py new file mode 100644 index 0000000..a43ae96 --- /dev/null +++ b/agent-fight/gpt/api-blaster-example.py @@ -0,0 +1,28 @@ +import importlib.util +from pathlib import Path + +import requests + + +spec = importlib.util.spec_from_file_location( + "api_blaster", Path(__file__).with_name("api-blaster.py") +) +api_blaster = importlib.util.module_from_spec(spec) +spec.loader.exec_module(api_blaster) + + +class ApiBlasterExample(api_blaster.ApiBlaster): + def run(self): + url = "https://api.justinsweb.com/prod/echo" + payload = {"message": "Hello from api-blaster"} + response = self.api.post(url, json=payload) + try: + response.raise_for_status() + print(response.text) + except requests.HTTPError as e: + print(f"HTTP error: {e}") + print(f"Response content: {response.text}") + + +if __name__ == "__main__": + ApiBlasterExample().run() diff --git a/agent-fight/gpt/api-blaster.py b/agent-fight/gpt/api-blaster.py new file mode 100644 index 0000000..3cdc673 --- /dev/null +++ b/agent-fight/gpt/api-blaster.py @@ -0,0 +1,106 @@ +import json +import pathlib +import requests +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)) + + +class ApiBlaster: + """Base class that handles OAuth configuration and session creation.""" + + def __init__(self): + self.conf = self._load_config() + self.client_creds = { + "client_id": self.conf["client_id"], + "client_secret": self.conf["client_secret"], + } + self.api = self._load_api_session() + + def _load_config(self): + """Load a saved config or prompt for values.""" + try: + 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): + 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: ") + default_redirect = "https://localhost:9090/cb" + conf["redirect_uri"] = ( + input(f"Enter your Redirect URL [{default_redirect}]: ") + or default_redirect + ) + + with open(conf_file_name, "w") as f: + f.write(json.dumps(conf)) + + return conf + + def _load_api_session(self): + """Load a saved token or get a new one and create an OAuth2 session.""" + try: + with open(auth_file_name, "r") as f: + token = json.load(f) + + # Quick sanity check + token["refresh_token"] + token["expires_at"] + + api = OAuth2Session( + client_id=self.conf["client_id"], + token=token, + auto_refresh_kwargs=self.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!") + + api = OAuth2Session( + client_id=self.conf["client_id"], + scope=scope, + redirect_uri=self.conf["redirect_uri"], + auto_refresh_kwargs=self.client_creds, + auto_refresh_url=refresh_url, + token_updater=token_updater, + ) + + authorization_url, state = api.authorization_url(authorization_base_url) + print("Please go here and authorize: ", authorization_url) + + redirect_response = input("Paste the full redirect URL here: ") + + token = api.fetch_token( + token_url, + client_secret=self.conf["client_secret"], + authorization_response=redirect_response, + ) + + token_updater(token) + + return api + + def run(self): + raise NotImplementedError("Subclasses should implement the run method.") + + +__all__ = ["ApiBlaster"] diff --git a/agent-fight/gpt/pyproject.toml b/agent-fight/gpt/pyproject.toml new file mode 100644 index 0000000..3736a96 --- /dev/null +++ b/agent-fight/gpt/pyproject.toml @@ -0,0 +1,10 @@ +[project] +name = "api-blaster-gpt" +version = "0.1.0" +description = "Script that gets OAuth out of your way and lets you automate tasks against any API" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "requests>=2.32.5", + "requests-oauthlib>=2.0.0", +] diff --git a/agent-fight/gpt/uv.lock b/agent-fight/gpt/uv.lock new file mode 100644 index 0000000..58f9a41 --- /dev/null +++ b/agent-fight/gpt/uv.lock @@ -0,0 +1,113 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "api-blaster-gpt" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "requests" }, + { name = "requests-oauthlib" }, +] + +[package.metadata] +requires-dist = [ + { name = "requests", specifier = ">=2.32.5" }, + { name = "requests-oauthlib", specifier = ">=2.0.0" }, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "oauthlib" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] diff --git a/agent-fight/justinsweb-api.md b/agent-fight/justinsweb-api.md new file mode 100644 index 0000000..01e08c7 --- /dev/null +++ b/agent-fight/justinsweb-api.md @@ -0,0 +1,9 @@ +## Authorization + +- Authorization is a standard OAuth2 flow hosted via Cognito on AWS +- justin@justinsweb.com +- hmj!NKD@kcg8ncy8jpv +- https://cognito.justinsweb.com/login?client_id=2vs5s0vrs39t3d6f5c7lv5pp46&response_type=code&scope=email+openid+phone&redirect_uri=https%3A%2F%2Foauth.pstmn.io%2Fv1%2Fbrowser-callback +- Client ID is 2vs5s0vrs39t3d6f5c7lv5pp46 +- Client Secret is 1lekp8qp9h534hhvir3fiof98k7oom0k4o9u3rst9ebvunkafqld +- Callback url is https://oauth.pstmn.io/v1/browser-callback \ No newline at end of file diff --git a/agent-fight/results.md b/agent-fight/results.md new file mode 100644 index 0000000..bdd7247 --- /dev/null +++ b/agent-fight/results.md @@ -0,0 +1,5 @@ +# Results + +1. I liked Claude and working with Claude Code best. That is what landed on the main branch. +2. Code for GPT is in the gpt directory. Repo was nuked so PRs were not preserved. +3. Code for Copilot is in the copilot directory. Repo was nuked so PRs were not preserved. \ No newline at end of file diff --git a/agent-fight/tasks.md b/agent-fight/tasks.md new file mode 100644 index 0000000..c7828e7 --- /dev/null +++ b/agent-fight/tasks.md @@ -0,0 +1,188 @@ +# Task List + +1. Initial repository setup (Claude.md, AGENTS.md, etc) +2. Refactor from asp* to api-blaster* +3. Move from asp api to justinsweb.com api +4. Refactor into a class that can be overridden with just the actionable bits visible + +## Copilot Coding Agent (assign issues to copilot) + +### Task 1 + +- Asked copilot to add custom instructions via https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions and the agent chat at https://github.com/copilot/agents +- Checked In + +### Task 2 + +- Created issue with... + +``` +This project needs to be generalized to a generic API client. Hence, we will no longer be calling it "asp" for Alexa Smart Properties. + +1. Rename all files and folders, existing and generated, from "asp" to "api-blaster". This inclides all "asp" and ".asp" prefixed file and folders. +2. Update all documentation to reflect this update. Include README.md as well as any agent files (copilot-instructions.md, CLAUDE.md, AGENTS.md, etc.) +``` + +- A Couple minutes later it was all set with the PR. Easy. + +### Task 3 + +- Created issue with + +``` +Need to move the Oauth configuration to a new server as well as update our API call to a new URL and definition. + +The new Oauth server definition is as follows... +1. Auth URL = https://cognito.justinsweb.com/oauth2/authorize +2. Access Token URL = https://cognito.justinsweb.com/oauth2/token +3. Refresh Token URL = Access Token URL +4. Scope = jw/admin +5. Callback URL defaults to https://localhost:9090/cb + +The API definition is to be changed from the current v2/endpoints as follows... +1. url = https://api.justinsweb.com/prod/echo +2. METHOD = POST +3. Body is raw json with structure {"message": "YOUR MESSAGE HERE"} +4. Response Body is just a string. +``` + +- Really didn't want to add the default callback URL as it could be a security issue in prod. I said it's fine in the code review and off it went. But then it just added the default without asking for input. One more try! Had to fight for a bit it it finally all worked out. + +### Task 4 + +- Created new issue with + +``` +We want to encapsulate the oauth logic so that many different script files can use the same Oauth config. + +Keep all the Oauth logic inside api-blaster.py. Prepare this file so that it can be extended by individual script files. +Create a new file called api-blaster-example.py. This file will extend from api-blaster.py and simply contain the api calling logic pulled out from the bottom of the original api-blaster.py file. +Hope that makes sense! +``` + +I just accepted what it did and tested. Worked. + +## Claude Code (Sonnet 4) + +### Task 1 + +- `/init` # took a couple seconds +- Checked in + +### Task 2 + +- Prompted Claude Code with + +``` +This project needs to be generalized to a generic API client. Hence, we will no longer be calling it "asp" for Alexa Smart Properties. + +1. Rename all files and folders, existing and generated, from "asp" to "api-blaster". This inclides all "asp" and ".asp" prefixed file and folders. +2. Update all documentation to reflect this update. Include README.md as well as any agent files (copilot-instructions.md, CLAUDE.md, AGENTS.md, etc.) +``` + +- Finished pretty quickly. If I had asked it to do the work on a feature branch we could have done this via standard github pr. + +### Task 3 + +- Prompted with with... + +``` +Need to move the Oauth configuration to a new server as well as update our API call to a new URL and definition. + +The new Oauth server definition is as follows... +1. Auth URL = https://cognito.justinsweb.com/oauth2/authorize +2. Access Token URL = https://cognito.justinsweb.com/oauth2/token +3. Refresh Token URL = Access Token URL +4. Scope = jw/admin +5. Callback URL defaults to https://localhost:9090/cb + +The API definition is to be changed from the current v2/endpoints as follows... +1. url = https://api.justinsweb.com/prod/echo +2. METHOD = POST +3. Body is raw json with structure {"message": "YOUR MESSAGE HERE"} +4. Response Body is just a string. +``` + +- Told it to use a feature branch and it did. Created a pull request. Nice. + +### Task 4 + +- Created issue with below to see if Claude can just look at my issues... + +``` +We want to encapsulate the oauth logic so that many different script files can use the same Oauth config. + +Keep all the Oauth logic inside api-blaster.py. Prepare this file so that it can be extended by individual script files. +Create a new file called api-blaster-example.py. This file will extend from api-blaster.py and simply contain the api calling logic pulled out from the bottom of the original api-blaster.py file. +Hope that makes sense! +``` + +I just accepted what it did and tested. Worked. + +## GPT aka Codex + +### Task 1 + +- https://chatgpt.com/codex -> More aligned with Copilot Coding Agent +- Allowed full internet access to the single repository +- Asked for a description of the codebase (this took about a minute - ui never quit though, forever loop) +- Doesn't seem to need an instructions file. + +> "An AGENTS.md isn’t strictly required, but it can be useful if you have specific instructions or conventions you want automated tools like me to follow. If your repository lacks such guidance—or you foresee contributors benefiting from standardized instructions—creating one can improve consistency. Otherwise, I can proceed without it." + +- Forced and AGENTS.md to be created + +> "Examine the codebase and create an AGENTS.md file" + +- Checked in + +### Task 2 + +- Created issue with... + +``` +This project needs to be generalized to a generic API client. Hence, we will no longer be calling it "asp" for Alexa Smart Properties. + +1. Rename all files and folders, existing and generated, from "asp" to "api-blaster". This inclides all "asp" and ".asp" prefixed file and folders. +2. Update all documentation to reflect this update. Include README.md as well as any agent files (copilot-instructions.md, CLAUDE.md, AGENTS.md, etc.) +``` + +- Took longer than the other two to complete. +- I had to remind it to add .api-blaster to .gitignore + +### Task 3 + +- Created issue with... + +``` +Need to move the Oauth configuration to a new server as well as update our API call to a new URL and definition. + +The new Oauth server definition is as follows... +1. Auth URL = https://cognito.justinsweb.com/oauth2/authorize +2. Access Token URL = https://cognito.justinsweb.com/oauth2/token +3. Refresh Token URL = Access Token URL +4. Scope = jw/admin +5. Callback URL defaults to https://localhost:9090/cb + +The API definition is to be changed from the current v2/endpoints as follows... +1. url = https://api.justinsweb.com/prod/echo +2. METHOD = POST +3. Body is raw json with structure {"message": "YOUR MESSAGE HERE"} +4. Response Body is just a string. +``` + +- Slower than the others but well done! + +### Task 4 + +- Created issue with... + +``` +We want to encapsulate the oauth logic so that many different script files can use the same Oauth config. + +Keep all the Oauth logic inside api-blaster.py. Prepare this file so that it can be extended by individual script files. +Create a new file called api-blaster-example.py. This file will extend from api-blaster.py and simply contain the api calling logic pulled out from the bottom of the original api-blaster.py file. +Hope that makes sense! +``` + +I just accepted what it did and tested. Worked. From ebdb1d3ad0e01182739c2245d53e549b9f1a5634 Mon Sep 17 00:00:00 2001 From: Justin Pecott Date: Mon, 13 Oct 2025 19:12:21 -0700 Subject: [PATCH 2/2] Update agent-fight/justinsweb-api.md scrub the sensitive info please Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- agent-fight/justinsweb-api.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/agent-fight/justinsweb-api.md b/agent-fight/justinsweb-api.md index 01e08c7..7e38445 100644 --- a/agent-fight/justinsweb-api.md +++ b/agent-fight/justinsweb-api.md @@ -1,9 +1,9 @@ ## Authorization - Authorization is a standard OAuth2 flow hosted via Cognito on AWS -- justin@justinsweb.com -- hmj!NKD@kcg8ncy8jpv -- https://cognito.justinsweb.com/login?client_id=2vs5s0vrs39t3d6f5c7lv5pp46&response_type=code&scope=email+openid+phone&redirect_uri=https%3A%2F%2Foauth.pstmn.io%2Fv1%2Fbrowser-callback -- Client ID is 2vs5s0vrs39t3d6f5c7lv5pp46 -- Client Secret is 1lekp8qp9h534hhvir3fiof98k7oom0k4o9u3rst9ebvunkafqld +- +- +- https://cognito.justinsweb.com/login?client_id=&response_type=code&scope=email+openid+phone&redirect_uri=https%3A%2F%2Foauth.pstmn.io%2Fv1%2Fbrowser-callback +- Client ID is +- Client Secret is - Callback url is https://oauth.pstmn.io/v1/browser-callback \ No newline at end of file