diff --git a/pulsenetwork-data/.env.example.template b/pulsenetwork-data/.env.example.template new file mode 100644 index 0000000..dd380a1 --- /dev/null +++ b/pulsenetwork-data/.env.example.template @@ -0,0 +1,12 @@ +# Wallet the agent pays from. Use a THROWAWAY key holding a few USDC on Base, +# never a wallet you keep funds in. This is the only credential you need: +# PulseNetwork has no accounts, no signup and no API keys. +PULSE_WALLET_KEY=0xyour-throwaway-private-key + +# Model for the agent (any browser-use supported provider works) +OPENAI_API_KEY=sk-your-key-here + +# Optional spending limits, enforced in code on every paid call. +# Defaults: 0.50 per call, 2.00 per session. +PULSE_MAX_PER_CALL_USD=0.50 +PULSE_SESSION_BUDGET_USD=2.00 diff --git a/pulsenetwork-data/README.md b/pulsenetwork-data/README.md new file mode 100644 index 0000000..f591fad --- /dev/null +++ b/pulsenetwork-data/README.md @@ -0,0 +1,126 @@ +# Agent Buys Live Data Mid-Task (x402) + +A browser-use agent that pays for the data it needs, per call, while it works. + +Most agents are stuck with whatever the model already knows plus whatever a page +happens to show. This template gives the agent a wallet and three tools, so when +a task needs a fact the model cannot know (is this token a honeypot, is this +flight owed compensation, has this product been recalled) the agent finds the +right endpoint, checks the price, pays a few cents in USDC on Base, and keeps +going. + +There is no signup, no API key and no account. The wallet is the identity. + +## The Tools + +| Tool | Cost | What it does | +| --- | --- | --- | +| `pulse_catalog` | free | Searches 950+ pay-per-call endpoints. Returns each match as a complete URL with its price and its query parameters. | +| `pulse_price` | free | Reads the exact price of one endpoint from its 402 challenge. Settles nothing. | +| `pulse_buy` | the endpoint price | Pays one call with USDC on Base and returns the JSON. | + +Typical prices run from $0.005 to $0.35 per call. The demo task costs about +$0.015. + +## Spending Controls + +The limits are in the code, not in the prompt, so the agent cannot be talked +past them by a web page or by its own reasoning: + +- **Host allowlist.** Every tool that makes an outbound request refuses any URL + that is not an https PulseNetwork endpoint, the free ones included. The agent + reads live web pages while it works, so a page must not be able to talk it + into fetching an internal address on the agent's behalf. +- **Per-call cap and session budget.** Enforced as an x402 payment policy, which + inspects the 402 challenge that is actually being signed. A price that changes + between the quote and the payment cannot slip through, because the quote is + not what authorizes the spend. +- **USDC on Base only.** The caps are counted in USDC's six decimals, so the + policy pins the asset as well as the network. The same number of atomic units + in a token with different decimals would be a different amount of money, and + an unpinned cap would not notice. +- **A lock around the buy path**, so two concurrent tool calls cannot both spend + the last of the budget. +- **The key never reaches the model.** It is read from the environment inside the + tool. The agent passes a URL and gets JSON back. + +## Setup + +### 1. Navigate to the project directory + +```bash +cd pulsenetwork-data +``` + +### 2. Create a wallet and fund it + +Generate a throwaway key and send it a few dollars of USDC on Base. Do not use a +wallet that holds anything you care about. A dollar or two is enough for +hundreds of calls. + +### 3. Configure the environment + +```bash +cp .env.example .env +``` + +Edit `.env` and set `PULSE_WALLET_KEY` and your model key. You can also lower +`PULSE_MAX_PER_CALL_USD` and `PULSE_SESSION_BUDGET_USD` from their defaults of +$0.50 and $2.00. + +### 4. Install dependencies + +```bash +uv sync +``` + +This installs `browser-use`, the official `x402` Python SDK with its httpx and +EVM extras, and `eth-account`. + +### 5. Run it + +```bash +uv run main.py +``` + +The demo task scans a real token on Robinhood Chain for rug and honeypot risk +before anyone buys it, and reports the verdict with its red and green flags. + +## Using It In Your Own Agent + +Import the `tools` object and hand it to any `Agent`: + +```python +from main import tools + +agent = Agent(task="...", llm=ChatOpenAI(model="gpt-4.1-mini"), tools=tools) +``` + +The tools compose with browser-use's own actions, so an agent can read a page, +buy a fact that the page does not contain, and act on both. + +## Troubleshooting + +| What you see | What it means | +| --- | --- | +| `Refused: PULSE_WALLET_KEY is not set` | No `.env`, or the key line is still the shipped placeholder. | +| `Refused: PULSE_WALLET_KEY is not a valid private key` | The key is set to something that is not a 0x private key. | +| `Refused: these tools only reach PulseNetwork endpoints` | The agent tried a URL outside PulseNetwork. Working as intended. | +| `Refused, nothing was paid: the endpoint asked for $X` | The price is above your remaining allowance. Raise the cap or pick a cheaper endpoint. | +| `Refused: the session budget is spent` | Restart the process, or raise `PULSE_SESSION_BUDGET_USD`. | +| `The payment did not complete` | Usually an unfunded wallet. Check the USDC balance on Base. | +| `Endpoint answered 400` | A missing or malformed parameter. `pulse_catalog` lists what each endpoint takes. | + +## Links + +- Catalog and docs: https://pulse.theaslangroupllc.com/llms.txt +- Machine-readable search: https://pulse.theaslangroupllc.com/api/catalog?q=token+safety +- x402 protocol: https://x402.org + +## Disclosure + +PulseNetwork is operated by The Aslan Group LLC, who wrote this template. The +tools only pay PulseNetwork endpoints, which is a deliberate safety limit rather +than a claim that nothing else is worth buying. The pattern itself is generic: +any x402 seller can be reached the same way by changing the host allowlist and +the catalog URL. diff --git a/pulsenetwork-data/__pycache__/main.cpython-312.pyc b/pulsenetwork-data/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000..c13a80a Binary files /dev/null and b/pulsenetwork-data/__pycache__/main.cpython-312.pyc differ diff --git a/pulsenetwork-data/main.py b/pulsenetwork-data/main.py new file mode 100644 index 0000000..c6eee02 --- /dev/null +++ b/pulsenetwork-data/main.py @@ -0,0 +1,310 @@ +"""PulseNetwork data tools: your agent buys live data mid-task with x402. + +Adds three tools to any browser-use agent: + - pulse_catalog: free search across 950+ pay-per-call data endpoints + - pulse_price: free price check for one endpoint (a bare 402 quote) + - pulse_buy: pay one endpoint with USDC on Base and return its JSON + +Payments use the official x402 Python SDK. No API keys, no accounts: the wallet +is the identity. The private key stays in an env var; the LLM never sees it. + +Three controls live in code, not in the prompt, so the agent cannot talk its way +past them: + - host allowlist: every tool that makes an outbound request refuses any URL + that is not an https PulseNetwork endpoint, the free ones included + - per-call cap and session budget: enforced as an x402 payment policy, so the + caps are checked against the 402 challenge that is actually signed, and + against USDC on Base specifically, since that is the unit they count in + - a lock around the buy path, so two concurrent calls cannot both spend the + last of the budget + +Docs: https://pulse.theaslangroupllc.com/llms.txt +""" + +import asyncio +import os + +import httpx +from browser_use import ActionResult, Agent, ChatOpenAI, Tools +from dotenv import load_dotenv +from eth_account import Account +from x402.client import x402Client +from x402.http.clients.httpx import x402HttpxClient +from x402.mechanisms.evm.exact import ExactEvmScheme +from x402.mechanisms.evm.signers import EthAccountSigner + +load_dotenv() + +CATALOG_URL = "https://pulse.theaslangroupllc.com/api/catalog" +ALLOWED_HOST_SUFFIX = ".theaslangroupllc.com" +BASE_NETWORK = "eip155:8453" +# The caps are counted in USDC's 6 decimals, so the asset has to be pinned too. +# The same number of atomic units in an 8-decimal or 18-decimal token would be a +# completely different amount of money, and the cap would not notice. +BASE_USDC = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913" +USDC_UNITS = 10**6 + +# Must match the placeholder shipped in .env.example.template. +PLACEHOLDER_KEY_PREFIX = "0xyour" + +MAX_PER_CALL_USD = float(os.getenv("PULSE_MAX_PER_CALL_USD", "0.50")) +SESSION_BUDGET_USD = float(os.getenv("PULSE_SESSION_BUDGET_USD", "2.00")) + +tools = Tools() +_spent = {"usd": 0.0} +_buy_lock = asyncio.Lock() + + +class _PaymentGuard: + """Binds the budget caps to the challenge the SDK actually signs. + + Quoting the price and then paying are two separate HTTP requests, so a quote + taken beforehand proves nothing about what the second request will be asked + to sign. This runs as an x402 payment policy instead: it sees the real 402 + challenge inside the paid request and drops anything that is not USDC on + Base within budget. If nothing survives, the SDK raises and no payload is + ever created, so nothing is signed and nothing settles. + """ + + def __init__(self, allowance_usd: float) -> None: + self.allowance_usd = allowance_usd + self._allowance_atomic = int(round(allowance_usd * USDC_UNITS)) + self.refusal: str | None = None + self.signed_usd = 0.0 + + def policy(self, _version: int, requirements: list) -> list: + keep = [] + for req in requirements: + if getattr(req, "network", None) != BASE_NETWORK: + continue + if str(getattr(req, "asset", "")).lower() != BASE_USDC: + self.refusal = ( + "the endpoint asked to be paid in a token other than USDC on Base, " + "which the budget caps cannot price" + ) + continue + amount = int(req.get_amount()) + if amount > self._allowance_atomic: + self.refusal = ( + f"the endpoint asked for ${amount / USDC_UNITS:.3f}, more than the " + f"${self.allowance_usd:.3f} left under the per-call cap and session budget" + ) + continue + keep.append(req) + if not keep and self.refusal is None: + self.refusal = "the endpoint offered no USDC-on-Base payment option" + return keep + + def record(self, ctx) -> None: + # Fires once per created payload, before it is sent. Accumulates rather + # than overwrites: if the SDK retries with a fresh payload, count both. + # Over-counting only shrinks the budget; under-counting would overspend. + self.signed_usd += int(ctx.selected_requirements.get_amount()) / USDC_UNITS + + +MAX_PARAMS_SHOWN = 6 + + +def _describe(entry: dict) -> str: + """One endpoint as the agent needs it: full URL, price, and its parameters.""" + price = entry.get("price_usd") + tag = "FREE" if not price else f"${price:.3f}" + lines = [ + f"{tag} {entry.get('method', 'GET')} {entry['url']}", + f" {entry.get('description', '')}", + ] + params = list((entry.get("params") or {}).items()) + # Required parameters first: a call fails without them, and some endpoints + # carry a dozen optional ones that would swamp the model's context. + params.sort(key=lambda kv: not kv[1].get("required")) + for name, spec in params[:MAX_PARAMS_SHOWN]: + need = "required" if spec.get("required") else "optional" + example = spec.get("example") + hint = f", e.g. {example}" if example is not None else "" + lines.append(f" - {name} ({need}): {spec.get('description', '')}{hint}") + if len(params) > MAX_PARAMS_SHOWN: + lines.append(f" - plus {len(params) - MAX_PARAMS_SHOWN} more optional parameters") + return "\n".join(lines) + + +def _reject_url(url: str) -> str | None: + """Refusal message if this URL is not a PulseNetwork endpoint, else None. + + Every tool that makes an outbound request runs this, not just the paying one. + The agent reads live web pages while it works, so a page can try to talk it + into fetching an internal address; a free tool with no allowlist would still + make that request on the agent's behalf. + """ + try: + parsed = httpx.URL(url) + except Exception: + return f"Refused: {url!r} is not a usable URL." + if parsed.scheme != "https": + return "Refused: only https PulseNetwork URLs are allowed." + if not (parsed.host or "").endswith(ALLOWED_HOST_SUFFIX): + return "Refused: these tools only reach PulseNetwork endpoints." + return None + + +async def _quote_usd(url: str) -> float | None: + """Ask an endpoint what it costs. A bare 402 quote is free and settles nothing. + + Matches the same asset the payment policy will accept, not just the same + network. Reading an amount denominated in some other Base token and dividing + it by USDC's decimals would print a confident and meaningless dollar figure, + for a challenge the buy path is going to refuse anyway. + """ + try: + async with httpx.AsyncClient(timeout=30) as client: + r = await client.get(url) + if r.status_code != 402: + return None + for accept in r.json().get("accepts", []): + if accept.get("network") != BASE_NETWORK: + continue + if str(accept.get("asset", "")).lower() != BASE_USDC: + continue + return int(accept["amount"]) / USDC_UNITS + except Exception: + return None + return None + + +@tools.action( + description=( + "Search the PulseNetwork catalog of 950+ pay-per-call data endpoints: " + "crypto token safety, market scans, travel rights, sports, climate, " + "compliance and more. Free. Returns each match as a complete URL with " + "its price and its query parameters, ready to pass to pulse_buy." + ) +) +async def pulse_catalog(query: str) -> ActionResult: + try: + async with httpx.AsyncClient(timeout=30) as client: + r = await client.get(CATALOG_URL, params={"q": query, "limit": 6}) + r.raise_for_status() + results = r.json().get("results") or [] + except Exception as exc: + return ActionResult( + extracted_content=f"The PulseNetwork catalog is unreachable right now ({exc}); nothing was searched." + ) + if not results: + return ActionResult( + extracted_content=( + f'No PulseNetwork endpoint matches "{query}". Try one broad noun instead, ' + 'such as "token", "flight", "recall", "sanctions" or "weather".' + ) + ) + body = "\n".join(_describe(e) for e in results) + return ActionResult( + extracted_content=( + f"PulseNetwork endpoints matching '{query}' (append the parameters as a query string, " + f"then call pulse_buy with the full URL):\n{body}" + ) + ) + + +@tools.action( + description="Check the exact USD price of a PulseNetwork endpoint before buying. Free." +) +async def pulse_price(url: str) -> ActionResult: + refusal = _reject_url(url) + if refusal: + return ActionResult(extracted_content=refusal) + price = await _quote_usd(url) + if price is None: + return ActionResult(extracted_content="No USDC-on-Base x402 quote at that URL.") + return ActionResult( + extracted_content=( + f"{url} costs ${price:.3f} per call. " + f"${SESSION_BUDGET_USD - _spent['usd']:.2f} of the session budget is left." + ) + ) + + +@tools.action( + description=( + "Buy one PulseNetwork data call with USDC on Base and return its JSON. " + "Use pulse_catalog first to get the full endpoint URL and its parameters." + ) +) +async def pulse_buy(url: str) -> ActionResult: + refusal = _reject_url(url) + if refusal: + return ActionResult(extracted_content=refusal) + + key = (os.getenv("PULSE_WALLET_KEY") or "").strip() + # The shipped .env.example carries a placeholder, so an unedited copy has to + # read as "not configured" rather than as a broken key. + if not key or key.lower().startswith(PLACEHOLDER_KEY_PREFIX): + return ActionResult( + extracted_content=( + "Refused: PULSE_WALLET_KEY is not set, so no payment is possible. " + "Copy .env.example to .env and replace the placeholder with a funded " + "throwaway wallet key." + ) + ) + try: + signer = EthAccountSigner(Account.from_key(key)) + except Exception: + return ActionResult( + extracted_content=( + "Refused: PULSE_WALLET_KEY is not a valid private key " + "(expected 0x followed by 64 hex characters)." + ) + ) + + async with _buy_lock: + allowance = min(MAX_PER_CALL_USD, SESSION_BUDGET_USD - _spent["usd"]) + if allowance <= 0: + return ActionResult( + extracted_content=f"Refused: the ${SESSION_BUDGET_USD:.2f} session budget is spent." + ) + + guard = _PaymentGuard(allowance) + payer = x402Client() + payer.register(BASE_NETWORK, ExactEvmScheme(signer=signer)) + payer.register_policy(guard.policy) + payer.on_after_payment_creation(guard.record) + + try: + async with x402HttpxClient(payer, timeout=90) as http: + r = await http.get(url) + except Exception as exc: + _spent["usd"] += guard.signed_usd + if guard.refusal and not guard.signed_usd: + return ActionResult( + extracted_content=f"Refused, nothing was paid: {guard.refusal}." + ) + return ActionResult(extracted_content=f"The payment did not complete: {exc}") + + _spent["usd"] += guard.signed_usd + + if r.status_code != 200: + return ActionResult( + extracted_content=( + f"Endpoint answered {r.status_code}, nothing useful was bought. " + f"Check the parameters against pulse_catalog. Body: {r.text[:300]}" + ) + ) + return ActionResult(extracted_content=r.text[:6000]) + + +async def main(): + agent = Agent( + task=( + "Someone on a forum is hyping the token CLAWSTR at address " + "0xdca76ddec78cEd6449A70BD5Df5180ACa4a55386 on Robinhood Chain. " + "Before anyone buys it: use pulse_catalog to find the PulseNetwork " + "endpoint that scans an EVM token for safety, then call pulse_buy with " + "that endpoint plus address and chain=robinhood, and summarize the " + "verdict with its red and green flags." + ), + llm=ChatOpenAI(model="gpt-4.1-mini"), + tools=tools, + ) + await agent.run() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/pulsenetwork-data/pyproject.toml.template b/pulsenetwork-data/pyproject.toml.template new file mode 100644 index 0000000..1464349 --- /dev/null +++ b/pulsenetwork-data/pyproject.toml.template @@ -0,0 +1,12 @@ +[project] +name = "browser-use-pulsenetwork-data" +version = "0.1.0" +description = "Agent buys live data mid-task with x402 micropayments" +requires-python = ">=3.11" +dependencies = [ + "browser-use", + "x402[httpx,evm]>=2.15", + "httpx", + "eth-account", + "python-dotenv", +] diff --git a/templates.json b/templates.json index 0828e12..9b19b4d 100644 --- a/templates.json +++ b/templates.json @@ -55,7 +55,9 @@ "next_steps": [ { "title": "Navigate to project directory", - "commands": ["cd {template}"] + "commands": [ + "cd {template}" + ] }, { "title": "Set up your API key", @@ -75,14 +77,18 @@ }, { "title": "Install dependencies", - "commands": ["uv sync"] + "commands": [ + "uv sync" + ] }, { "title": "Run the script", - "commands": ["uv run {output}"] + "commands": [ + "uv run {output}" + ] }, { - "footer": "πŸ“– See README.md for cloud configuration options and troubleshooting" + "footer": "\ud83d\udcd6 See README.md for cloud configuration options and troubleshooting" } ], "author": { @@ -124,7 +130,9 @@ "next_steps": [ { "title": "Navigate to project directory", - "commands": ["cd {template}"] + "commands": [ + "cd {template}" + ] }, { "title": "Set up your API key", @@ -136,19 +144,25 @@ }, { "title": "Install dependencies", - "commands": ["uv sync"] + "commands": [ + "uv sync" + ] }, { "title": "Launch Chrome with debugging (in a separate terminal)", - "commands": ["python launch_chrome_debug.py"], + "commands": [ + "python launch_chrome_debug.py" + ], "note": "(Run with --help to see options like --profile)\n(Keep this terminal open!)" }, { "title": "Run your script (in a NEW terminal)", - "commands": ["cd {template} && uv run {output}"] + "commands": [ + "cd {template} && uv run {output}" + ] }, { - "footer": "πŸ“– See README.md for detailed instructions" + "footer": "\ud83d\udcd6 See README.md for detailed instructions" } ], "author": { @@ -194,7 +208,9 @@ "next_steps": [ { "title": "Navigate to project directory", - "commands": ["cd {template}"] + "commands": [ + "cd {template}" + ] }, { "title": "Set up your API key", @@ -206,7 +222,9 @@ }, { "title": "Install dependencies", - "commands": ["uv sync"] + "commands": [ + "uv sync" + ] }, { "title": "Customize your data", @@ -217,10 +235,12 @@ }, { "title": "Run the application", - "commands": ["uv run {output} --resume example_resume.pdf"] + "commands": [ + "uv run {output} --resume example_resume.pdf" + ] }, { - "footer": "πŸ“– See README.md for customization and troubleshooting" + "footer": "\ud83d\udcd6 See README.md for customization and troubleshooting" } ], "author": { @@ -261,7 +281,9 @@ "next_steps": [ { "title": "Navigate to project directory", - "commands": ["cd {template}"] + "commands": [ + "cd {template}" + ] }, { "title": "Set up your API keys", @@ -273,18 +295,22 @@ }, { "title": "Install dependencies", - "commands": ["uv sync"] + "commands": [ + "uv sync" + ] }, { "title": "Run the script", - "commands": ["uv run {output}"] + "commands": [ + "uv run {output}" + ] }, { - "footer": "πŸ“– See README.md for customization and advanced usage" + "footer": "\ud83d\udcd6 See README.md for customization and advanced usage" } ], "author": { - "name": "Magnus MΓΌller", + "name": "Magnus M\u00fcller", "github_profile": "https://github.com/MagMueller", "last_modified_date": "2025-11-11" } @@ -317,7 +343,9 @@ "next_steps": [ { "title": "Navigate to project directory", - "commands": ["cd {template}"] + "commands": [ + "cd {template}" + ] }, { "title": "Set up your API keys", @@ -329,14 +357,18 @@ }, { "title": "Install dependencies", - "commands": ["uv sync"] + "commands": [ + "uv sync" + ] }, { "title": "Run the comparison", - "commands": ["uv run {output}"] + "commands": [ + "uv run {output}" + ] }, { - "footer": "πŸ“– See README.md for customization and advanced usage\n\n⚑ Enter a task and watch LLMs race to complete it!" + "footer": "\ud83d\udcd6 See README.md for customization and advanced usage\n\n\u26a1 Enter a task and watch LLMs race to complete it!" } ], "author": { @@ -377,7 +409,9 @@ "next_steps": [ { "title": "Navigate to project directory", - "commands": ["cd {template}"] + "commands": [ + "cd {template}" + ] }, { "title": "Set up your API keys", @@ -389,7 +423,9 @@ }, { "title": "Install dependencies", - "commands": ["uv sync"] + "commands": [ + "uv sync" + ] }, { "title": "Create and configure Slack app", @@ -400,15 +436,19 @@ }, { "title": "Start ngrok tunnel (for local development)", - "commands": ["ngrok http 8000"], + "commands": [ + "ngrok http 8000" + ], "note": "(Keep this terminal open and copy the HTTPS URL)" }, { "title": "Run the application (in a NEW terminal)", - "commands": ["cd {template} && uv run app/main.py"] + "commands": [ + "cd {template} && uv run app/main.py" + ] }, { - "footer": "πŸ“– See README.md for detailed setup, Event Subscriptions configuration, and production deployment" + "footer": "\ud83d\udcd6 See README.md for detailed setup, Event Subscriptions configuration, and production deployment" } ], "author": { @@ -446,7 +486,9 @@ "next_steps": [ { "title": "Navigate to project directory", - "commands": ["cd {template}"] + "commands": [ + "cd {template}" + ] }, { "title": "Set up your API key", @@ -458,14 +500,18 @@ }, { "title": "Install dependencies", - "commands": ["uv sync"] + "commands": [ + "uv sync" + ] }, { "title": "Run the job scraper", - "commands": ["uv run {output}"] + "commands": [ + "uv run {output}" + ] }, { - "footer": "πŸ“– See README.md for customization and troubleshooting\n\nπŸ€– CodeAgent will generate extraction code and save it to script.ipynb" + "footer": "\ud83d\udcd6 See README.md for customization and troubleshooting\n\n\ud83e\udd16 CodeAgent will generate extraction code and save it to script.ipynb" } ], "author": { @@ -511,7 +557,9 @@ "next_steps": [ { "title": "Navigate to project directory", - "commands": ["cd {template}"] + "commands": [ + "cd {template}" + ] }, { "title": "Set up your API key and cloud settings", @@ -523,14 +571,18 @@ }, { "title": "Install dependencies", - "commands": ["uv sync"] + "commands": [ + "uv sync" + ] }, { "title": "Run the scheduler", - "commands": ["uv run {output}"] + "commands": [ + "uv run {output}" + ] }, { - "footer": "πŸ“– See README.md for adding custom agents, configuration, and troubleshooting\n\nπŸ”„ The scheduler will auto-discover all .py files in the agents/ directory\n\nπŸ“¦ For an always-on VPS with Telegram control, see Browser Use Box: https://browser-use.com/bux" + "footer": "\ud83d\udcd6 See README.md for adding custom agents, configuration, and troubleshooting\n\n\ud83d\udd04 The scheduler will auto-discover all .py files in the agents/ directory\n\n\ud83d\udce6 For an always-on VPS with Telegram control, see Browser Use Box: https://browser-use.com/bux" } ], "author": { @@ -538,5 +590,67 @@ "github_profile": "https://github.com/ShawnPana", "last_modified_date": "2025-11-14" } + }, + "pulsenetwork-data": { + "file": "pulsenetwork-data/main.py", + "description": "Agent buys live data mid-task with x402 micropayments (token safety, markets, 950+ endpoints) - USDC on Base, no API keys, code-enforced budget caps", + "files": [ + { + "source": "pulsenetwork-data/main.py", + "dest": "main.py" + }, + { + "source": "pulsenetwork-data/pyproject.toml.template", + "dest": "pyproject.toml" + }, + { + "source": "gitignore.template", + "dest": ".gitignore" + }, + { + "source": "pulsenetwork-data/.env.example.template", + "dest": ".env.example" + }, + { + "source": "pulsenetwork-data/README.md", + "dest": "README.md" + } + ], + "next_steps": [ + { + "title": "Navigate to project directory", + "commands": [ + "cd {template}" + ] + }, + { + "title": "Set up your payment wallet and model key", + "commands": [ + "cp .env.example .env", + "# Edit .env: add PULSE_WALLET_KEY and OPENAI_API_KEY" + ], + "note": "(PULSE_WALLET_KEY is a THROWAWAY key holding a few USDC on Base. PulseNetwork has no signup and no API key.)" + }, + { + "title": "Install dependencies", + "commands": [ + "uv sync" + ] + }, + { + "title": "Run the agent", + "commands": [ + "uv run {output}" + ] + }, + { + "footer": "\ud83d\udcb8 Spending is capped in code: $0.50 per call and $2.00 per session by default, tunable in .env\n\n\ud83d\udd0e Browse the catalog free at https://pulse.theaslangroupllc.com/llms.txt\n\n\ud83d\udcd6 See README.md for the safety controls and troubleshooting" + } + ], + "author": { + "name": "PulseNetwork", + "github_profile": "https://github.com/GTCC777", + "last_modified_date": "2026-08-09" + } } }