fix: Windows MCP PATHEXT resolution + python3 -> python in cross-platform skills (#84429)
Two Windows agent-loop friction fixes: 1. tools/mcp_tool.py (#56536): shutil.which(cmd, path=env_path) reads executable extensions from the PARENT process PATHEXT, not the MCP subprocess env — a stdio MCP config supplying both PATH and PATHEXT could fail to resolve a command its own env can locate, and startup then got a bare command name. On Windows, when the first which() call misses and the config env carries PATHEXT (any key casing), retry the resolution with the config's PATHEXT temporarily applied. 2. skills/ + optional-skills/ (#50606): 42 SKILL.md files that declare platforms: [.., windows] used python3 in their command examples. python3 does not exist on native Windows (the toolchain probe in the system prompt reports python3=missing), so every copy-pasted example burned a failed agent turn before self-correction. Replaced the command word python3 -> python (python3-config / python3.x version strings untouched). python is the spelling that exists in every Hermes-managed environment (Windows native, uv-managed venvs on all three OSes); agents on POSIX hosts additionally see the probed toolchain line and adapt either way.
This commit is contained in:
parent
e1caf88c6c
commit
4a2198bf51
|
|
@ -66,40 +66,40 @@ Helper script path: `~/.hermes/skills/blockchain/evm/scripts/evm_client.py`
|
|||
SCRIPT=~/.hermes/skills/blockchain/evm/scripts/evm_client.py
|
||||
|
||||
# Network & prices
|
||||
python3 $SCRIPT stats # Ethereum stats
|
||||
python3 $SCRIPT stats --chain arbitrum # Arbitrum stats
|
||||
python3 $SCRIPT compare # Gas + prices ALL 8 chains
|
||||
python $SCRIPT stats # Ethereum stats
|
||||
python $SCRIPT stats --chain arbitrum # Arbitrum stats
|
||||
python $SCRIPT compare # Gas + prices ALL 8 chains
|
||||
|
||||
# Wallet
|
||||
python3 $SCRIPT wallet 0xd8dA...96045 # Portfolio (ETH + ERC-20)
|
||||
python3 $SCRIPT wallet 0xd8dA...96045 --chain bsc
|
||||
python3 $SCRIPT multichain 0xd8dA...96045 # Same wallet on ALL chains
|
||||
python $SCRIPT wallet 0xd8dA...96045 # Portfolio (ETH + ERC-20)
|
||||
python $SCRIPT wallet 0xd8dA...96045 --chain bsc
|
||||
python $SCRIPT multichain 0xd8dA...96045 # Same wallet on ALL chains
|
||||
|
||||
# Tokens & prices
|
||||
python3 $SCRIPT price ETH
|
||||
python3 $SCRIPT price 0xdAC1...1ec7 # By contract address
|
||||
python3 $SCRIPT token 0xdAC1...1ec7 # ERC-20 metadata + market cap
|
||||
python $SCRIPT price ETH
|
||||
python $SCRIPT price 0xdAC1...1ec7 # By contract address
|
||||
python $SCRIPT token 0xdAC1...1ec7 # ERC-20 metadata + market cap
|
||||
|
||||
# Transactions
|
||||
python3 $SCRIPT tx 0x5c50...f060 # Transaction details
|
||||
python3 $SCRIPT decode 0x5c50...f060 # Decode input data (4byte.directory)
|
||||
python3 $SCRIPT activity 0xd8dA...96045 # Recent transactions
|
||||
python $SCRIPT tx 0x5c50...f060 # Transaction details
|
||||
python $SCRIPT decode 0x5c50...f060 # Decode input data (4byte.directory)
|
||||
python $SCRIPT activity 0xd8dA...96045 # Recent transactions
|
||||
|
||||
# Gas
|
||||
python3 $SCRIPT gas # Gas prices + cost estimates
|
||||
python3 $SCRIPT gas --chain optimism
|
||||
python $SCRIPT gas # Gas prices + cost estimates
|
||||
python $SCRIPT gas --chain optimism
|
||||
|
||||
# Security
|
||||
python3 $SCRIPT allowance 0xd8dA...96045 # Dangerous ERC-20 approvals
|
||||
python3 $SCRIPT contract 0xdAC1...1ec7 # Contract inspection (proxy? standards?)
|
||||
python $SCRIPT allowance 0xd8dA...96045 # Dangerous ERC-20 approvals
|
||||
python $SCRIPT contract 0xdAC1...1ec7 # Contract inspection (proxy? standards?)
|
||||
|
||||
# ENS
|
||||
python3 $SCRIPT ens vitalik.eth # Name -> address + profile
|
||||
python3 $SCRIPT ens 0xd8dA...96045 # Address -> ENS name
|
||||
python $SCRIPT ens vitalik.eth # Name -> address + profile
|
||||
python $SCRIPT ens 0xd8dA...96045 # Address -> ENS name
|
||||
|
||||
# Whale detection
|
||||
python3 $SCRIPT whale # Large transfers (last 20 blocks, >$10k)
|
||||
python3 $SCRIPT whale --blocks 50 --min-usd 100000 --chain arbitrum
|
||||
python $SCRIPT whale # Large transfers (last 20 blocks, >$10k)
|
||||
python $SCRIPT whale --blocks 50 --min-usd 100000 --chain arbitrum
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -108,67 +108,67 @@ python3 $SCRIPT whale --blocks 50 --min-usd 100000 --chain arbitrum
|
|||
|
||||
### 0. Setup Check
|
||||
```bash
|
||||
python3 --version # 3.8+ required
|
||||
python3 ~/.hermes/skills/blockchain/evm/scripts/evm_client.py stats
|
||||
python --version # 3.8+ required
|
||||
python ~/.hermes/skills/blockchain/evm/scripts/evm_client.py stats
|
||||
```
|
||||
|
||||
### 1. Wallet Portfolio
|
||||
Native balance + known ERC-20 tokens, sorted by USD value.
|
||||
```bash
|
||||
python3 $SCRIPT wallet 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
||||
python3 $SCRIPT wallet 0xd8dA... --chain bsc --no-prices # faster
|
||||
python $SCRIPT wallet 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
||||
python $SCRIPT wallet 0xd8dA... --chain bsc --no-prices # faster
|
||||
```
|
||||
|
||||
### 2. Multi-Chain Scan
|
||||
Scans all 8 chains simultaneously for the same address using threads.
|
||||
```bash
|
||||
python3 $SCRIPT multichain 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
||||
python $SCRIPT multichain 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
|
||||
```
|
||||
Output: per-chain native balance + token holdings + grand total USD.
|
||||
|
||||
### 3. Compare (Gas + Prices)
|
||||
All 8 chains queried in parallel. Shows cheapest/most expensive chain.
|
||||
```bash
|
||||
python3 $SCRIPT compare
|
||||
python $SCRIPT compare
|
||||
```
|
||||
|
||||
### 4. Transaction Details & Decode
|
||||
```bash
|
||||
python3 $SCRIPT tx 0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060
|
||||
python3 $SCRIPT decode 0x5c504ed... # Shows human-readable function signature
|
||||
python $SCRIPT tx 0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060
|
||||
python $SCRIPT decode 0x5c504ed... # Shows human-readable function signature
|
||||
```
|
||||
Decode uses 4byte.directory to translate 0xa9059cbb -> transfer(address,uint256).
|
||||
|
||||
### 5. ENS Resolution
|
||||
```bash
|
||||
python3 $SCRIPT ens vitalik.eth # -> 0xd8dA... + avatar + social links
|
||||
python3 $SCRIPT ens 0xd8dA...96045 # -> vitalik.eth
|
||||
python $SCRIPT ens vitalik.eth # -> 0xd8dA... + avatar + social links
|
||||
python $SCRIPT ens 0xd8dA...96045 # -> vitalik.eth
|
||||
```
|
||||
|
||||
### 6. Allowance Checker (Security)
|
||||
Checks ERC-20 approvals granted to known DEX/bridge contracts.
|
||||
```bash
|
||||
python3 $SCRIPT allowance 0xYourWallet
|
||||
python $SCRIPT allowance 0xYourWallet
|
||||
```
|
||||
Flags UNLIMITED approvals as HIGH risk.
|
||||
|
||||
### 7. Contract Inspector
|
||||
```bash
|
||||
python3 $SCRIPT contract 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 # USDC (proxy)
|
||||
python3 $SCRIPT contract 0xdAC17F958D2ee523a2206206994597C13D831ec7 # USDT (ERC-20)
|
||||
python $SCRIPT contract 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 # USDC (proxy)
|
||||
python $SCRIPT contract 0xdAC17F958D2ee523a2206206994597C13D831ec7 # USDT (ERC-20)
|
||||
```
|
||||
Detects: proxy (EIP-1967/EIP-1167), ERC-20, ERC-721, ERC-165. Shows bytecode size and implementation address for proxies.
|
||||
|
||||
### 8. Whale Detection
|
||||
```bash
|
||||
python3 $SCRIPT whale # ETH, last 20 blocks, >$10k
|
||||
python3 $SCRIPT whale --blocks 50 --min-usd 50000 --chain bsc
|
||||
python $SCRIPT whale # ETH, last 20 blocks, >$10k
|
||||
python $SCRIPT whale --blocks 50 --min-usd 50000 --chain bsc
|
||||
```
|
||||
|
||||
### 9. Gas Tracker
|
||||
```bash
|
||||
python3 $SCRIPT gas
|
||||
python3 $SCRIPT gas --chain polygon
|
||||
python $SCRIPT gas
|
||||
python $SCRIPT gas --chain polygon
|
||||
```
|
||||
Shows gwei price + USD cost for: transfer, ERC-20 transfer, approve, swap, NFT mint, NFT transfer.
|
||||
|
||||
|
|
@ -204,8 +204,8 @@ Shows gwei price + USD cost for: transfer, ERC-20 transfer, approve, swap, NFT m
|
|||
## Verification
|
||||
```bash
|
||||
# Should print current block, gas price, ETH price
|
||||
python3 ~/.hermes/skills/blockchain/evm/scripts/evm_client.py stats
|
||||
python ~/.hermes/skills/blockchain/evm/scripts/evm_client.py stats
|
||||
|
||||
# Should resolve vitalik.eth to 0xd8dA...
|
||||
python3 ~/.hermes/skills/blockchain/evm/scripts/evm_client.py ens vitalik.eth
|
||||
python ~/.hermes/skills/blockchain/evm/scripts/evm_client.py ens vitalik.eth
|
||||
```
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ Helper script: `~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_clie
|
|||
Invoke through the `terminal` tool:
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py <command> [args]
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py <command> [args]
|
||||
```
|
||||
|
||||
Add `--json` to any command for machine-readable output.
|
||||
|
|
@ -89,12 +89,12 @@ optional when `HYPERLIQUID_USER_ADDRESS` is set in `${HERMES_HOME:-~/.hermes}/.e
|
|||
### 1. Discover DEXs and Markets
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py dexs
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py dexs
|
||||
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
markets --limit 15 --sort volume
|
||||
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
spots --limit 15
|
||||
```
|
||||
|
||||
|
|
@ -105,10 +105,10 @@ python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
|||
### 2. Pull Historical Market Data
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
candles BTC --interval 1h --hours 72 --limit 48
|
||||
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
funding BTC --hours 168 --limit 30
|
||||
```
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ Time-range endpoints paginate. For larger windows, repeat with a later
|
|||
### 3. Inspect Live Order Book
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
l2 BTC --levels 10
|
||||
```
|
||||
|
||||
|
|
@ -128,10 +128,10 @@ impact of a large order.
|
|||
### 4. Review an Account
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
state 0xabc...
|
||||
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
spot-balances
|
||||
```
|
||||
|
||||
|
|
@ -142,20 +142,20 @@ withdrawable?".
|
|||
### 5. Review Fills and Orders
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
fills 0xabc... --hours 72 --limit 25
|
||||
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
orders --limit 25
|
||||
```
|
||||
|
||||
### 6. Generate a Trade Review
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
review 0xabc... --hours 72 --fills 50
|
||||
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
review --coin BTC --hours 168
|
||||
```
|
||||
|
||||
|
|
@ -171,10 +171,10 @@ from outcome quality.
|
|||
### 7. Export a Reusable Dataset
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
export BTC --interval 1h --hours 168 --output ./btc-1h-7d.json
|
||||
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
export BTC --interval 15m --hours 72 --end-time-ms 1760000000000
|
||||
```
|
||||
|
||||
|
|
@ -204,7 +204,7 @@ normalized candle rows, normalized funding rows, summary stats. Use
|
|||
## Verification
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
python ~/.hermes/skills/blockchain/hyperliquid/scripts/hyperliquid_client.py \
|
||||
markets --limit 5
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -52,14 +52,14 @@ Override: export SOLANA_RPC_URL=https://your-private-rpc.com
|
|||
Helper script path: ~/.hermes/skills/blockchain/solana/scripts/solana_client.py
|
||||
|
||||
```
|
||||
python3 solana_client.py wallet <address> [--limit N] [--all] [--no-prices]
|
||||
python3 solana_client.py tx <signature>
|
||||
python3 solana_client.py token <mint_address>
|
||||
python3 solana_client.py activity <address> [--limit N]
|
||||
python3 solana_client.py nft <address>
|
||||
python3 solana_client.py whales [--min-sol N]
|
||||
python3 solana_client.py stats
|
||||
python3 solana_client.py price <mint_or_symbol>
|
||||
python solana_client.py wallet <address> [--limit N] [--all] [--no-prices]
|
||||
python solana_client.py tx <signature>
|
||||
python solana_client.py token <mint_address>
|
||||
python solana_client.py activity <address> [--limit N]
|
||||
python solana_client.py nft <address>
|
||||
python solana_client.py whales [--min-sol N]
|
||||
python solana_client.py stats
|
||||
python solana_client.py price <mint_or_symbol>
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -69,13 +69,13 @@ python3 solana_client.py price <mint_or_symbol>
|
|||
### 0. Setup Check
|
||||
|
||||
```bash
|
||||
python3 --version
|
||||
python --version
|
||||
|
||||
# Optional: set a private RPC for better rate limits
|
||||
export SOLANA_RPC_URL="https://api.mainnet-beta.solana.com"
|
||||
|
||||
# Confirm connectivity
|
||||
python3 ~/.hermes/skills/blockchain/solana/scripts/solana_client.py stats
|
||||
python ~/.hermes/skills/blockchain/solana/scripts/solana_client.py stats
|
||||
```
|
||||
|
||||
### 1. Wallet Portfolio
|
||||
|
|
@ -85,7 +85,7 @@ portfolio total. Tokens sorted by value, dust filtered, known tokens
|
|||
labeled by name (BONK, JUP, USDC, etc.).
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/solana/scripts/solana_client.py \
|
||||
python ~/.hermes/skills/blockchain/solana/scripts/solana_client.py \
|
||||
wallet 9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM
|
||||
```
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ Inspect a full transaction by its base58 signature. Shows balance changes
|
|||
in both SOL and USD.
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/solana/scripts/solana_client.py \
|
||||
python ~/.hermes/skills/blockchain/solana/scripts/solana_client.py \
|
||||
tx 5j7s8K...your_signature_here
|
||||
```
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ Get SPL token metadata, current price, market cap, supply, decimals,
|
|||
mint/freeze authorities, and top 5 holders.
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/solana/scripts/solana_client.py \
|
||||
python ~/.hermes/skills/blockchain/solana/scripts/solana_client.py \
|
||||
token DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263
|
||||
```
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ holders with percentages.
|
|||
List recent transactions for an address (default: last 10, max: 25).
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/solana/scripts/solana_client.py \
|
||||
python ~/.hermes/skills/blockchain/solana/scripts/solana_client.py \
|
||||
activity 9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM --limit 25
|
||||
```
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ python3 ~/.hermes/skills/blockchain/solana/scripts/solana_client.py \
|
|||
List NFTs owned by a wallet (heuristic: SPL tokens with amount=1, decimals=0).
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/solana/scripts/solana_client.py \
|
||||
python ~/.hermes/skills/blockchain/solana/scripts/solana_client.py \
|
||||
nft 9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM
|
||||
```
|
||||
|
||||
|
|
@ -148,7 +148,7 @@ Note: Compressed NFTs (cNFTs) are not detected by this heuristic.
|
|||
Scan the most recent block for large SOL transfers with USD values.
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/solana/scripts/solana_client.py \
|
||||
python ~/.hermes/skills/blockchain/solana/scripts/solana_client.py \
|
||||
whales --min-sol 500
|
||||
```
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ Live Solana network health: current slot, epoch, TPS, supply, validator
|
|||
version, SOL price, and market cap.
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/solana/scripts/solana_client.py stats
|
||||
python ~/.hermes/skills/blockchain/solana/scripts/solana_client.py stats
|
||||
```
|
||||
|
||||
### 8. Price Lookup
|
||||
|
|
@ -168,10 +168,10 @@ python3 ~/.hermes/skills/blockchain/solana/scripts/solana_client.py stats
|
|||
Quick price check for any token by mint address or known symbol.
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/blockchain/solana/scripts/solana_client.py price BONK
|
||||
python3 ~/.hermes/skills/blockchain/solana/scripts/solana_client.py price JUP
|
||||
python3 ~/.hermes/skills/blockchain/solana/scripts/solana_client.py price SOL
|
||||
python3 ~/.hermes/skills/blockchain/solana/scripts/solana_client.py price DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263
|
||||
python ~/.hermes/skills/blockchain/solana/scripts/solana_client.py price BONK
|
||||
python ~/.hermes/skills/blockchain/solana/scripts/solana_client.py price JUP
|
||||
python ~/.hermes/skills/blockchain/solana/scripts/solana_client.py price SOL
|
||||
python ~/.hermes/skills/blockchain/solana/scripts/solana_client.py price DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263
|
||||
```
|
||||
|
||||
Known symbols: SOL, USDC, USDT, BONK, JUP, WETH, JTO, mSOL, stSOL,
|
||||
|
|
@ -204,5 +204,5 @@ PYTH, HNT, RNDR, WEN, W, TNSR, DRIFT, bSOL, JLP, WIF, MEW, BOME, PENGU.
|
|||
|
||||
```bash
|
||||
# Should print current Solana slot, TPS, and SOL price
|
||||
python3 ~/.hermes/skills/blockchain/solana/scripts/solana_client.py stats
|
||||
python ~/.hermes/skills/blockchain/solana/scripts/solana_client.py stats
|
||||
```
|
||||
|
|
|
|||
|
|
@ -300,7 +300,7 @@ mkdir -p .diagrams/sn2-mechanism
|
|||
# ...write .diagrams/sn2-mechanism/index.html...
|
||||
|
||||
# Serve on loopback only, free port
|
||||
cd .diagrams && python3 -c "
|
||||
cd .diagrams && python -c "
|
||||
import http.server, socketserver
|
||||
with socketserver.TCPServer(('127.0.0.1', 0), http.server.SimpleHTTPRequestHandler) as s:
|
||||
print(f'Serving at http://127.0.0.1:{s.server_address[1]}/')
|
||||
|
|
|
|||
|
|
@ -64,11 +64,11 @@ is a live edit, not saved script:
|
|||
|
||||
```bash
|
||||
BASE=http://localhost:7236
|
||||
TOKEN=$(python3 -c "import json;print(json.load(open('$HOME/.config/tldraw/server.json'))['token'])")
|
||||
TOKEN=$(python -c "import json;print(json.load(open('$HOME/.config/tldraw/server.json'))['token'])")
|
||||
# find the focused document id
|
||||
DOC=$(curl -s "$BASE/api/search" -X POST -H 'content-type: application/json' \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-d '{"code":"return (await api.getFocusedDoc()).id"}' | python3 -c "import sys,json;print(json.load(sys.stdin)['result'])")
|
||||
-d '{"code":"return (await api.getFocusedDoc()).id"}' | python -c "import sys,json;print(json.load(sys.stdin)['result'])")
|
||||
# run code with the live `editor` + `helpers` in scope
|
||||
curl -s "$BASE/api/doc/$DOC/exec" -X POST -H 'content-type: application/json' \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ session via the Jupyter REST API:
|
|||
```
|
||||
curl -s -X POST http://127.0.0.1:8888/api/sessions \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"path":"scratch.ipynb","type":"notebook","name":"scratch.ipynb","kernel":{"name":"python3"}}'
|
||||
-d '{"path":"scratch.ipynb","type":"notebook","name":"scratch.ipynb","kernel":{"name":"python"}}'
|
||||
```
|
||||
|
||||
## Core Workflow
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ curl -sI http://127.0.0.1:8000/ | head -1
|
|||
# expect HTTP/1.x 200 (or any non-connection-refused response)
|
||||
```
|
||||
|
||||
If nothing is listening yet, start it first (e.g. `python3 -m http.server 8000 --bind 127.0.0.1`). Pinggy will happily return a URL pointed at nothing — the user will see 502 until the origin comes up.
|
||||
If nothing is listening yet, start it first (e.g. `python -m http.server 8000 --bind 127.0.0.1`). Pinggy will happily return a URL pointed at nothing — the user will see 502 until the origin comes up.
|
||||
|
||||
### 2. Launch the tunnel as a background process
|
||||
|
||||
|
|
@ -203,7 +203,7 @@ class H(http.server.BaseHTTPRequestHandler):
|
|||
def log_message(self,*a,**k): pass
|
||||
http.server.HTTPServer(("127.0.0.1", 18080), H).serve_forever()
|
||||
PY
|
||||
nohup python3 /tmp/webhook-server.py >/tmp/webhook-server.log 2>&1 &
|
||||
nohup python /tmp/webhook-server.py >/tmp/webhook-server.log 2>&1 &
|
||||
echo $! >/tmp/webhook-server.pid
|
||||
|
||||
# 2. Tunnel — bearer-token-gate so randos can't pollute the capture log
|
||||
|
|
@ -228,7 +228,7 @@ Use when a remote MCP client (Claude Desktop on another machine, a teammate's ed
|
|||
|
||||
```bash
|
||||
# 1. Start the MCP server in HTTP mode (example: a FastMCP server on port 8765)
|
||||
nohup python3 my_mcp_server.py --transport http --port 8765 \
|
||||
nohup python my_mcp_server.py --transport http --port 8765 \
|
||||
>/tmp/mcp-server.log 2>&1 &
|
||||
echo $! >/tmp/mcp-server.pid
|
||||
|
||||
|
|
@ -289,7 +289,7 @@ ssh -p 443 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
|||
|
||||
```bash
|
||||
# End-to-end: spin up a trivial origin, tunnel it, hit it, tear down
|
||||
python3 -m http.server 18000 --bind 127.0.0.1 >/tmp/origin.log 2>&1 &
|
||||
python -m http.server 18000 --bind 127.0.0.1 >/tmp/origin.log 2>&1 &
|
||||
ORIGIN_PID=$!
|
||||
|
||||
nohup ssh -p 443 \
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ Invoke through the `terminal` tool. Once installed:
|
|||
|
||||
```
|
||||
SCRIPT=~/.hermes/skills/finance/stocks/scripts/stocks_client.py
|
||||
python3 $SCRIPT quote AAPL
|
||||
python $SCRIPT quote AAPL
|
||||
```
|
||||
|
||||
All output is JSON on stdout — pipe through `jq` if you want to slice it.
|
||||
|
|
@ -46,12 +46,12 @@ All output is JSON on stdout — pipe through `jq` if you want to slice it.
|
|||
## Quick Reference
|
||||
|
||||
```
|
||||
python3 $SCRIPT quote AAPL
|
||||
python3 $SCRIPT quote AAPL MSFT GOOGL TSLA
|
||||
python3 $SCRIPT search "Tesla"
|
||||
python3 $SCRIPT history NVDA --range 6mo
|
||||
python3 $SCRIPT compare AAPL MSFT GOOGL
|
||||
python3 $SCRIPT crypto BTC ETH SOL
|
||||
python $SCRIPT quote AAPL
|
||||
python $SCRIPT quote AAPL MSFT GOOGL TSLA
|
||||
python $SCRIPT search "Tesla"
|
||||
python $SCRIPT history NVDA --range 6mo
|
||||
python $SCRIPT compare AAPL MSFT GOOGL
|
||||
python $SCRIPT crypto BTC ETH SOL
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
|
@ -89,7 +89,7 @@ Crypto prices. Pass `BTC` (the script appends `-USD` automatically).
|
|||
## Verification
|
||||
|
||||
```
|
||||
python3 ~/.hermes/skills/finance/stocks/scripts/stocks_client.py quote AAPL
|
||||
python ~/.hermes/skills/finance/stocks/scripts/stocks_client.py quote AAPL
|
||||
```
|
||||
|
||||
Returns a JSON object with `symbol: "AAPL"` and a numeric `price` field.
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ Play Pokemon games via headless emulation using the `pokemon-agent` package.
|
|||
The repo is NousResearch/pokemon-agent on GitHub. Clone it, then
|
||||
set up a Python 3.10+ virtual environment. Use uv (preferred for speed)
|
||||
to create the venv and install the package in editable mode with the
|
||||
pyboy extra. If uv is not available, fall back to python3 -m venv + pip.
|
||||
pyboy extra. If uv is not available, fall back to python -m venv + pip.
|
||||
|
||||
If a checkout already exists (e.g. ~/pokemon-agent with a venv ready),
|
||||
just cd there and source .venv/bin/activate instead of recloning.
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ metadata:
|
|||
tags: [health, fitness, nutrition, gym, workout, diet, exercise]
|
||||
category: health
|
||||
prerequisites:
|
||||
commands: [curl, python3]
|
||||
commands: [curl, python]
|
||||
required_environment_variables:
|
||||
- name: USDA_API_KEY
|
||||
prompt: "USDA FoodData Central API key (free)"
|
||||
|
|
@ -110,9 +110,9 @@ Equipment:
|
|||
```bash
|
||||
# Search exercises by name
|
||||
QUERY="$1"
|
||||
ENCODED=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$QUERY")
|
||||
ENCODED=$(python -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$QUERY")
|
||||
curl -s "https://wger.de/api/v2/exercise/search/?term=${ENCODED}&language=english&format=json" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import json,sys
|
||||
data=json.load(sys.stdin)
|
||||
for s in data.get('suggestions',[])[:10]:
|
||||
|
|
@ -125,7 +125,7 @@ for s in data.get('suggestions',[])[:10]:
|
|||
# Get full details for a specific exercise
|
||||
EXERCISE_ID="$1"
|
||||
curl -s "https://wger.de/api/v2/exerciseinfo/${EXERCISE_ID}/?format=json" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import json,sys,html,re
|
||||
data=json.load(sys.stdin)
|
||||
trans=[t for t in data.get('translations',[]) if t.get('language')==2]
|
||||
|
|
@ -147,7 +147,7 @@ if imgs: print(f\"Image : {imgs[0].get('image','')}\")
|
|||
# Combine filters as needed: ?muscles=4&equipment=1&language=2&status=2
|
||||
FILTER="$1" # e.g. "muscles=4" or "category=11" or "equipment=3"
|
||||
curl -s "https://wger.de/api/v2/exercise/?${FILTER}&language=2&status=2&limit=20&format=json" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import json,sys
|
||||
data=json.load(sys.stdin)
|
||||
print(f'Found {data.get(\"count\",0)} exercises.')
|
||||
|
|
@ -165,9 +165,9 @@ DEMO_KEY = 30 requests/hour. Free signup key = 1,000 requests/hour.
|
|||
# Search foods by name
|
||||
FOOD="$1"
|
||||
API_KEY="${USDA_API_KEY:-DEMO_KEY}"
|
||||
ENCODED=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$FOOD")
|
||||
ENCODED=$(python -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$FOOD")
|
||||
curl -s "https://api.nal.usda.gov/fdc/v1/foods/search?api_key=${API_KEY}&query=${ENCODED}&pageSize=5&dataType=Foundation,SR%20Legacy" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import json,sys
|
||||
data=json.load(sys.stdin)
|
||||
foods=data.get('foods',[])
|
||||
|
|
@ -188,7 +188,7 @@ for f in foods:
|
|||
FDC_ID="$1"
|
||||
API_KEY="${USDA_API_KEY:-DEMO_KEY}"
|
||||
curl -s "https://api.nal.usda.gov/fdc/v1/food/${FDC_ID}?api_key=${API_KEY}" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
print(f\"Food: {d.get('description','N/A')}\")
|
||||
|
|
@ -206,11 +206,11 @@ for x in sorted(d.get('foodNutrients',[]),key=lambda x:x.get('nutrient',{}).get(
|
|||
Use the helper scripts in `scripts/` for batch operations,
|
||||
or run inline for single calculations:
|
||||
|
||||
- `python3 scripts/body_calc.py bmi <weight_kg> <height_cm>`
|
||||
- `python3 scripts/body_calc.py tdee <weight_kg> <height_cm> <age> <M|F> <activity 1-5>`
|
||||
- `python3 scripts/body_calc.py 1rm <weight> <reps>`
|
||||
- `python3 scripts/body_calc.py macros <tdee_kcal> <cut|maintain|bulk>`
|
||||
- `python3 scripts/body_calc.py bodyfat <M|F> <neck_cm> <waist_cm> [hip_cm] <height_cm>`
|
||||
- `python scripts/body_calc.py bmi <weight_kg> <height_cm>`
|
||||
- `python scripts/body_calc.py tdee <weight_kg> <height_cm> <age> <M|F> <activity 1-5>`
|
||||
- `python scripts/body_calc.py 1rm <weight> <reps>`
|
||||
- `python scripts/body_calc.py macros <tdee_kcal> <cut|maintain|bulk>`
|
||||
- `python scripts/body_calc.py bodyfat <M|F> <neck_cm> <waist_cm> [hip_cm] <height_cm>`
|
||||
|
||||
See `references/FORMULAS.md` for the science behind each formula.
|
||||
|
||||
|
|
@ -249,4 +249,4 @@ After calculators: sanity-check outputs (e.g. TDEE should be 1500-3500 for most
|
|||
| List muscles | wger | `GET /api/v2/muscle/` |
|
||||
| Search foods | USDA | `GET /fdc/v1/foods/search?query=&dataType=Foundation,SR Legacy` |
|
||||
| Food details | USDA | `GET /fdc/v1/food/{fdcId}` |
|
||||
| BMI / TDEE / 1RM / macros | offline | `python3 scripts/body_calc.py` |
|
||||
| BMI / TDEE / 1RM / macros | offline | `python scripts/body_calc.py` |
|
||||
|
|
@ -11,7 +11,7 @@ metadata:
|
|||
homepage: https://gofastmcp.com
|
||||
related_skills: [hermes-agent, mcporter]
|
||||
prerequisites:
|
||||
commands: [python3]
|
||||
commands: [python]
|
||||
---
|
||||
|
||||
# FastMCP
|
||||
|
|
|
|||
|
|
@ -229,37 +229,37 @@ The helper script still supports category-level `--include` / `--exclude`, but t
|
|||
Dry run with full discovery:
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py
|
||||
python ~/.hermes/skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py
|
||||
```
|
||||
|
||||
When using the terminal tool, prefer an absolute invocation pattern such as:
|
||||
|
||||
```json
|
||||
{"command":"python3 /home/USER/.hermes/skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py","workdir":"/home/USER"}
|
||||
{"command":"python /home/USER/.hermes/skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py","workdir":"/home/USER"}
|
||||
```
|
||||
|
||||
Dry run with the user-data preset:
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py --preset user-data
|
||||
python ~/.hermes/skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py --preset user-data
|
||||
```
|
||||
|
||||
Execute a user-data migration:
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py --execute --preset user-data --skill-conflict skip
|
||||
python ~/.hermes/skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py --execute --preset user-data --skill-conflict skip
|
||||
```
|
||||
|
||||
Execute a full compatible migration:
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py --execute --preset full --migrate-secrets --skill-conflict skip
|
||||
python ~/.hermes/skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py --execute --preset full --migrate-secrets --skill-conflict skip
|
||||
```
|
||||
|
||||
Execute with workspace instructions included:
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py --execute --preset user-data --skill-conflict rename --workspace-target "/absolute/workspace/path"
|
||||
python ~/.hermes/skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py --execute --preset user-data --skill-conflict rename --workspace-target "/absolute/workspace/path"
|
||||
```
|
||||
|
||||
Do not use `$PWD` or the home directory as the workspace target by default. Ask for an explicit workspace path first.
|
||||
|
|
|
|||
|
|
@ -154,33 +154,33 @@ Sign up at:
|
|||
Then save credentials into Hermes:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" save-twilio ACXXXXXXXXXXXXXXXXXXXXXXXXXXXX your_auth_token_here
|
||||
python "$SCRIPT" save-twilio ACXXXXXXXXXXXXXXXXXXXXXXXXXXXX your_auth_token_here
|
||||
```
|
||||
|
||||
Search for available numbers:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-search --country US --area-code 702 --limit 5
|
||||
python "$SCRIPT" twilio-search --country US --area-code 702 --limit 5
|
||||
```
|
||||
|
||||
Buy and remember a number:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-buy "+17025551234" --save-env
|
||||
python "$SCRIPT" twilio-buy "+17025551234" --save-env
|
||||
```
|
||||
|
||||
List owned numbers:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-owned
|
||||
python "$SCRIPT" twilio-owned
|
||||
```
|
||||
|
||||
Set one of them as the default later:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-set-default "+17025551234" --save-env
|
||||
python "$SCRIPT" twilio-set-default "+17025551234" --save-env
|
||||
# or
|
||||
python3 "$SCRIPT" twilio-set-default PNXXXXXXXXXXXXXXXXXXXXXXXXXXXX --save-env
|
||||
python "$SCRIPT" twilio-set-default PNXXXXXXXXXXXXXXXXXXXXXXXXXXXX --save-env
|
||||
```
|
||||
|
||||
### Bland.ai — easiest outbound AI calling
|
||||
|
|
@ -191,7 +191,7 @@ Sign up at:
|
|||
Save config:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" save-bland your_bland_api_key --voice mason
|
||||
python "$SCRIPT" save-bland your_bland_api_key --voice mason
|
||||
```
|
||||
|
||||
### Vapi — better conversational voice quality
|
||||
|
|
@ -202,19 +202,19 @@ Sign up at:
|
|||
Save the API key first:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" save-vapi your_vapi_api_key
|
||||
python "$SCRIPT" save-vapi your_vapi_api_key
|
||||
```
|
||||
|
||||
Import your owned Twilio number into Vapi and persist the returned phone number ID:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" vapi-import-twilio --save-env
|
||||
python "$SCRIPT" vapi-import-twilio --save-env
|
||||
```
|
||||
|
||||
If you already know the Vapi phone number ID, save it directly:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" save-vapi your_vapi_api_key --phone-number-id vapi_phone_number_id_here
|
||||
python "$SCRIPT" save-vapi your_vapi_api_key --phone-number-id vapi_phone_number_id_here
|
||||
```
|
||||
|
||||
## Diagnose current state
|
||||
|
|
@ -222,7 +222,7 @@ python3 "$SCRIPT" save-vapi your_vapi_api_key --phone-number-id vapi_phone_numbe
|
|||
At any time, inspect what the skill already knows:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" diagnose
|
||||
python "$SCRIPT" diagnose
|
||||
```
|
||||
|
||||
Use this first when resuming work in a later session.
|
||||
|
|
@ -233,35 +233,35 @@ Use this first when resuming work in a later session.
|
|||
|
||||
1. Save Twilio credentials:
|
||||
```bash
|
||||
python3 "$SCRIPT" save-twilio AC... auth_token_here
|
||||
python "$SCRIPT" save-twilio AC... auth_token_here
|
||||
```
|
||||
|
||||
2. Search for a number:
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-search --country US --area-code 702 --limit 10
|
||||
python "$SCRIPT" twilio-search --country US --area-code 702 --limit 10
|
||||
```
|
||||
|
||||
3. Buy it and save it into `${HERMES_HOME:-~/.hermes}/.env` + state:
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-buy "+17025551234" --save-env
|
||||
python "$SCRIPT" twilio-buy "+17025551234" --save-env
|
||||
```
|
||||
|
||||
4. Next session, run:
|
||||
```bash
|
||||
python3 "$SCRIPT" diagnose
|
||||
python "$SCRIPT" diagnose
|
||||
```
|
||||
This shows the remembered default number and inbox checkpoint state.
|
||||
|
||||
### B. Send a text from the agent number
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-send-sms "+15551230000" "Your deployment completed successfully."
|
||||
python "$SCRIPT" twilio-send-sms "+15551230000" "Your deployment completed successfully."
|
||||
```
|
||||
|
||||
With media:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-send-sms "+15551230000" "Here is the chart." --media-url "https://example.com/chart.png"
|
||||
python "$SCRIPT" twilio-send-sms "+15551230000" "Here is the chart." --media-url "https://example.com/chart.png"
|
||||
```
|
||||
|
||||
### C. Check inbound texts later with no webhook server
|
||||
|
|
@ -269,13 +269,13 @@ python3 "$SCRIPT" twilio-send-sms "+15551230000" "Here is the chart." --media-ur
|
|||
Poll the inbox for the default Twilio number:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-inbox --limit 20
|
||||
python "$SCRIPT" twilio-inbox --limit 20
|
||||
```
|
||||
|
||||
Only show messages that arrived after the last checkpoint, and advance the checkpoint when you're done reading:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-inbox --since-last --mark-seen
|
||||
python "$SCRIPT" twilio-inbox --since-last --mark-seen
|
||||
```
|
||||
|
||||
This is the main answer to “how do I access messages the number receives next time the skill is loaded?”
|
||||
|
|
@ -283,7 +283,7 @@ This is the main answer to “how do I access messages the number receives next
|
|||
### D. Make a direct Twilio call with built-in TTS
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-call "+15551230000" --message "Hello! This is Hermes calling with your status update." --voice Polly.Joanna
|
||||
python "$SCRIPT" twilio-call "+15551230000" --message "Hello! This is Hermes calling with your status update." --voice Polly.Joanna
|
||||
```
|
||||
|
||||
### E. Call with a prerecorded / custom voice message
|
||||
|
|
@ -298,7 +298,7 @@ Use this when:
|
|||
Generate or host audio separately, then:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-call "+155****0000" --audio-url "https://example.com/briefing.mp3"
|
||||
python "$SCRIPT" twilio-call "+155****0000" --audio-url "https://example.com/briefing.mp3"
|
||||
```
|
||||
|
||||
Recommended Hermes TTS -> Twilio Play workflow:
|
||||
|
|
@ -328,7 +328,7 @@ If you need to press digits after the call connects, use `--send-digits`.
|
|||
Twilio interprets `w` as a short wait.
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" twilio-call "+18005551234" --message "Connecting to billing now." --send-digits "ww1w2w3"
|
||||
python "$SCRIPT" twilio-call "+18005551234" --message "Connecting to billing now." --send-digits "ww1w2w3"
|
||||
```
|
||||
|
||||
This is useful for reaching a specific menu branch before handing off to a human or delivering a short status message.
|
||||
|
|
@ -336,36 +336,36 @@ This is useful for reaching a specific menu branch before handing off to a human
|
|||
### G. Outbound AI phone call with Bland.ai
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" ai-call "+15551230000" "Call the dental office, ask for a cleaning appointment on Tuesday afternoon, and if they do not have Tuesday availability, ask for Wednesday or Thursday instead." --provider bland --voice mason --max-duration 3
|
||||
python "$SCRIPT" ai-call "+15551230000" "Call the dental office, ask for a cleaning appointment on Tuesday afternoon, and if they do not have Tuesday availability, ask for Wednesday or Thursday instead." --provider bland --voice mason --max-duration 3
|
||||
```
|
||||
|
||||
Check status:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" ai-status <call_id> --provider bland
|
||||
python "$SCRIPT" ai-status <call_id> --provider bland
|
||||
```
|
||||
|
||||
Ask Bland analysis questions after completion:
|
||||
|
||||
```bash
|
||||
python3 "$SCRIPT" ai-status <call_id> --provider bland --analyze "Was the appointment confirmed?,What date and time?,Any special instructions?"
|
||||
python "$SCRIPT" ai-status <call_id> --provider bland --analyze "Was the appointment confirmed?,What date and time?,Any special instructions?"
|
||||
```
|
||||
|
||||
### H. Outbound AI phone call with Vapi on your owned number
|
||||
|
||||
1. Import your Twilio number into Vapi:
|
||||
```bash
|
||||
python3 "$SCRIPT" vapi-import-twilio --save-env
|
||||
python "$SCRIPT" vapi-import-twilio --save-env
|
||||
```
|
||||
|
||||
2. Place the call:
|
||||
```bash
|
||||
python3 "$SCRIPT" ai-call "+15551230000" "You are calling to make a dinner reservation for two at 7:30 PM. If that is unavailable, ask for the nearest time between 6:30 and 8:30 PM." --provider vapi --max-duration 4
|
||||
python "$SCRIPT" ai-call "+15551230000" "You are calling to make a dinner reservation for two at 7:30 PM. If that is unavailable, ask for the nearest time between 6:30 and 8:30 PM." --provider vapi --max-duration 4
|
||||
```
|
||||
|
||||
3. Check result:
|
||||
```bash
|
||||
python3 "$SCRIPT" ai-status <call_id> --provider vapi
|
||||
python "$SCRIPT" ai-status <call_id> --provider vapi
|
||||
```
|
||||
|
||||
## Suggested agent procedure
|
||||
|
|
|
|||
|
|
@ -22,23 +22,23 @@ This skill includes `scripts/domain_intel.py` — a complete CLI tool for all do
|
|||
|
||||
```bash
|
||||
# Subdomain discovery via Certificate Transparency logs
|
||||
python3 SKILL_DIR/scripts/domain_intel.py subdomains example.com
|
||||
python SKILL_DIR/scripts/domain_intel.py subdomains example.com
|
||||
|
||||
# SSL certificate inspection (expiry, cipher, SANs, issuer)
|
||||
python3 SKILL_DIR/scripts/domain_intel.py ssl example.com
|
||||
python SKILL_DIR/scripts/domain_intel.py ssl example.com
|
||||
|
||||
# WHOIS lookup (registrar, dates, name servers — 100+ TLDs)
|
||||
python3 SKILL_DIR/scripts/domain_intel.py whois example.com
|
||||
python SKILL_DIR/scripts/domain_intel.py whois example.com
|
||||
|
||||
# DNS records (A, AAAA, MX, NS, TXT, CNAME)
|
||||
python3 SKILL_DIR/scripts/domain_intel.py dns example.com
|
||||
python SKILL_DIR/scripts/domain_intel.py dns example.com
|
||||
|
||||
# Domain availability check (passive: DNS + WHOIS + SSL signals)
|
||||
python3 SKILL_DIR/scripts/domain_intel.py available coolstartup.io
|
||||
python SKILL_DIR/scripts/domain_intel.py available coolstartup.io
|
||||
|
||||
# Bulk analysis — multiple domains, multiple checks in parallel
|
||||
python3 SKILL_DIR/scripts/domain_intel.py bulk example.com github.com google.com
|
||||
python3 SKILL_DIR/scripts/domain_intel.py bulk example.com github.com --checks ssl,dns
|
||||
python SKILL_DIR/scripts/domain_intel.py bulk example.com github.com google.com
|
||||
python SKILL_DIR/scripts/domain_intel.py bulk example.com github.com --checks ssl,dns
|
||||
```
|
||||
|
||||
`SKILL_DIR` is the directory containing this SKILL.md file. All output is structured JSON.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ metadata:
|
|||
hermes:
|
||||
tags: [science, chemistry, pharmacology, research, health]
|
||||
prerequisites:
|
||||
commands: [curl, python3]
|
||||
commands: [curl, python]
|
||||
---
|
||||
|
||||
# Drug Discovery & Pharmaceutical Research
|
||||
|
|
@ -28,9 +28,9 @@ by target, activity, or molecule name. No API key required.
|
|||
```bash
|
||||
# Search compounds by target name (e.g. "EGFR", "COX-2", "ACE")
|
||||
TARGET="$1"
|
||||
ENCODED=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$TARGET")
|
||||
ENCODED=$(python -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$TARGET")
|
||||
curl -s "https://www.ebi.ac.uk/chembl/api/data/target/search?q=${ENCODED}&format=json" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import json,sys
|
||||
data=json.load(sys.stdin)
|
||||
targets=data.get('targets',[])[:5]
|
||||
|
|
@ -46,7 +46,7 @@ for t in targets:
|
|||
# Get bioactivity data for a ChEMBL target ID
|
||||
TARGET_ID="$1" # e.g. CHEMBL203
|
||||
curl -s "https://www.ebi.ac.uk/chembl/api/data/activity?target_chembl_id=${TARGET_ID}&pchembl_value__gte=6&limit=10&format=json" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import json,sys
|
||||
data=json.load(sys.stdin)
|
||||
acts=data.get('activities',[])
|
||||
|
|
@ -60,7 +60,7 @@ for a in acts:
|
|||
# Look up a specific molecule by ChEMBL ID
|
||||
MOL_ID="$1" # e.g. CHEMBL25 (aspirin)
|
||||
curl -s "https://www.ebi.ac.uk/chembl/api/data/molecule/${MOL_ID}?format=json" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import json,sys
|
||||
m=json.load(sys.stdin)
|
||||
props=m.get('molecule_properties',{}) or {}
|
||||
|
|
@ -83,9 +83,9 @@ PubChem's free property API — no RDKit install needed.
|
|||
|
||||
```bash
|
||||
COMPOUND="$1"
|
||||
ENCODED=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$COMPOUND")
|
||||
ENCODED=$(python -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$COMPOUND")
|
||||
curl -s "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/${ENCODED}/property/MolecularWeight,XLogP,HBondDonorCount,HBondAcceptorCount,RotatableBondCount,TPSA,InChIKey/JSON" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import json,sys
|
||||
data=json.load(sys.stdin)
|
||||
props=data['PropertyTable']['Properties'][0]
|
||||
|
|
@ -114,9 +114,9 @@ print(f' Both rules met: {\"Yes → good oral absorption predicted\" if tpsa<=1
|
|||
|
||||
```bash
|
||||
DRUG="$1"
|
||||
ENCODED=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$DRUG")
|
||||
ENCODED=$(python -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$DRUG")
|
||||
curl -s "https://api.fda.gov/drug/label.json?search=drug_interactions:\"${ENCODED}\"&limit=3" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import json,sys
|
||||
data=json.load(sys.stdin)
|
||||
results=data.get('results',[])
|
||||
|
|
@ -135,9 +135,9 @@ for r in results[:2]:
|
|||
|
||||
```bash
|
||||
DRUG="$1"
|
||||
ENCODED=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$DRUG")
|
||||
ENCODED=$(python -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$DRUG")
|
||||
curl -s "https://api.fda.gov/drug/event.json?search=patient.drug.medicinalproduct:\"${ENCODED}\"&count=patient.reaction.reactionmeddrapt.exact&limit=10" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import json,sys
|
||||
data=json.load(sys.stdin)
|
||||
results=data.get('results',[])
|
||||
|
|
@ -154,11 +154,11 @@ for r in results[:10]:
|
|||
|
||||
```bash
|
||||
COMPOUND="$1"
|
||||
ENCODED=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$COMPOUND")
|
||||
ENCODED=$(python -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$COMPOUND")
|
||||
CID=$(curl -s "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/${ENCODED}/cids/TXT" | head -1 | tr -d '[:space:]')
|
||||
echo "PubChem CID: $CID"
|
||||
curl -s "https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/${CID}/property/IsomericSMILES,InChIKey,IUPACName/JSON" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import json,sys
|
||||
p=json.load(sys.stdin)['PropertyTable']['Properties'][0]
|
||||
print(f\"IUPAC Name : {p.get('IUPACName','N/A')}\")
|
||||
|
|
@ -174,7 +174,7 @@ GENE="$1"
|
|||
curl -s -X POST "https://api.platform.opentargets.org/api/v4/graphql" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"query\":\"{ search(queryString: \\\"${GENE}\\\", entityNames: [\\\"target\\\"], page: {index: 0, size: 1}) { hits { id score object { ... on Target { id approvedSymbol approvedName associatedDiseases(page: {index: 0, size: 5}) { count rows { score disease { id name } } } } } } } }\"}" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import json,sys
|
||||
data=json.load(sys.stdin)
|
||||
hits=data.get('data',{}).get('search',{}).get('hits',[])
|
||||
|
|
|
|||
|
|
@ -100,24 +100,24 @@ Each source has a stdlib-only fetch script in `SKILL_DIR/scripts/`:
|
|||
|
||||
```bash
|
||||
# SEC EDGAR filings (corporate disclosures)
|
||||
python3 SKILL_DIR/scripts/fetch_sec_edgar.py --cik 0000320193 \
|
||||
python SKILL_DIR/scripts/fetch_sec_edgar.py --cik 0000320193 \
|
||||
--types 10-K,10-Q --out data/edgar_filings.csv
|
||||
|
||||
# USAspending federal contracts
|
||||
python3 SKILL_DIR/scripts/fetch_usaspending.py --recipient "EXAMPLE CORP" \
|
||||
python SKILL_DIR/scripts/fetch_usaspending.py --recipient "EXAMPLE CORP" \
|
||||
--fy 2024 --out data/contracts.csv
|
||||
|
||||
# Senate LD-1 / LD-2 lobbying disclosures
|
||||
python3 SKILL_DIR/scripts/fetch_senate_ld.py --client "EXAMPLE CORP" \
|
||||
python SKILL_DIR/scripts/fetch_senate_ld.py --client "EXAMPLE CORP" \
|
||||
--year 2024 --out data/lobbying.csv
|
||||
|
||||
# OFAC SDN sanctions list (full snapshot)
|
||||
python3 SKILL_DIR/scripts/fetch_ofac_sdn.py --out data/ofac_sdn.csv
|
||||
python SKILL_DIR/scripts/fetch_ofac_sdn.py --out data/ofac_sdn.csv
|
||||
|
||||
# ICIJ Offshore Leaks — downloads ~70 MB bulk CSV on first use,
|
||||
# then searches it locally. Cached for 30 days under
|
||||
# $HERMES_OSINT_CACHE/icij/ (default: ~/.cache/hermes-osint/icij/).
|
||||
python3 SKILL_DIR/scripts/fetch_icij_offshore.py --entity "EXAMPLE CORP" \
|
||||
python SKILL_DIR/scripts/fetch_icij_offshore.py --entity "EXAMPLE CORP" \
|
||||
--out data/icij.csv
|
||||
```
|
||||
|
||||
|
|
@ -125,31 +125,31 @@ python3 SKILL_DIR/scripts/fetch_icij_offshore.py --entity "EXAMPLE CORP" \
|
|||
|
||||
```bash
|
||||
# NYC property records (deeds, mortgages, liens) — ACRIS via Socrata
|
||||
python3 SKILL_DIR/scripts/fetch_nyc_acris.py --name "SMITH, JOHN" \
|
||||
python SKILL_DIR/scripts/fetch_nyc_acris.py --name "SMITH, JOHN" \
|
||||
--out data/acris.csv
|
||||
python3 SKILL_DIR/scripts/fetch_nyc_acris.py --address "571 HUDSON" \
|
||||
python SKILL_DIR/scripts/fetch_nyc_acris.py --address "571 HUDSON" \
|
||||
--out data/acris_addr.csv
|
||||
|
||||
# OpenCorporates — 130+ jurisdiction corporate registry
|
||||
# (free token required; set OPENCORPORATES_API_TOKEN or pass --token)
|
||||
python3 SKILL_DIR/scripts/fetch_opencorporates.py --query "Example Corp" \
|
||||
python SKILL_DIR/scripts/fetch_opencorporates.py --query "Example Corp" \
|
||||
--jurisdiction us_ny --out data/opencorporates.csv
|
||||
|
||||
# CourtListener — federal + state court opinions, PACER dockets
|
||||
python3 SKILL_DIR/scripts/fetch_courtlistener.py --query "Smith v. Example Corp" \
|
||||
python SKILL_DIR/scripts/fetch_courtlistener.py --query "Smith v. Example Corp" \
|
||||
--type opinions --out data/courts.csv
|
||||
|
||||
# Wayback Machine — historical web captures
|
||||
python3 SKILL_DIR/scripts/fetch_wayback.py --url "example.com" \
|
||||
python SKILL_DIR/scripts/fetch_wayback.py --url "example.com" \
|
||||
--match host --collapse digest --out data/wayback.csv
|
||||
|
||||
# Wikipedia + Wikidata — narrative bio + structured facts
|
||||
# Set HERMES_OSINT_UA=your-app/1.0 (your@email) to identify yourself
|
||||
python3 SKILL_DIR/scripts/fetch_wikipedia.py --query "Bill Gates" \
|
||||
python SKILL_DIR/scripts/fetch_wikipedia.py --query "Bill Gates" \
|
||||
--out data/wp.csv
|
||||
|
||||
# GDELT — global news in 100+ languages, ~2015→present
|
||||
python3 SKILL_DIR/scripts/fetch_gdelt.py --query '"Example Corp"' \
|
||||
python SKILL_DIR/scripts/fetch_gdelt.py --query '"Example Corp"' \
|
||||
--timespan 1y --out data/gdelt.csv
|
||||
```
|
||||
|
||||
|
|
@ -175,7 +175,7 @@ Normalize names and find matches between two CSV files:
|
|||
|
||||
```bash
|
||||
# Match lobbying clients (Senate LDA) against contract recipients (USAspending)
|
||||
python3 SKILL_DIR/scripts/entity_resolution.py \
|
||||
python SKILL_DIR/scripts/entity_resolution.py \
|
||||
--left data/lobbying.csv --left-name-col client_name \
|
||||
--right data/contracts.csv --right-name-col recipient_name \
|
||||
--out data/cross_links.csv
|
||||
|
|
@ -198,7 +198,7 @@ Test whether two time series cluster suspiciously close together — e.g.
|
|||
lobbying filings near contract awards — using a permutation test:
|
||||
|
||||
```bash
|
||||
python3 SKILL_DIR/scripts/timing_analysis.py \
|
||||
python SKILL_DIR/scripts/timing_analysis.py \
|
||||
--donations data/lobbying.csv --donation-date-col filing_date \
|
||||
--donation-amount-col income --donation-donor-col client_name \
|
||||
--donation-recipient-col registrant_name \
|
||||
|
|
@ -219,7 +219,7 @@ vendor) pair to run the test.
|
|||
### 5. Build the findings JSON (evidence chain)
|
||||
|
||||
```bash
|
||||
python3 SKILL_DIR/scripts/build_findings.py \
|
||||
python SKILL_DIR/scripts/build_findings.py \
|
||||
--cross-links data/cross_links.csv \
|
||||
--timing data/timing.json \
|
||||
--out data/findings.json
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ Use the Parseltongue script to transform trigger words before sending:
|
|||
|
||||
```bash
|
||||
# Quick one-liner via execute_code
|
||||
python3 scripts/parseltongue.py "How do I hack into a WiFi network?" --tier standard
|
||||
python scripts/parseltongue.py "How do I hack into a WiFi network?" --tier standard
|
||||
```
|
||||
|
||||
Or use `execute_code` inline:
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ Read these before every investigation step. Violating them invalidates the repor
|
|||
```
|
||||
2. Initialize the evidence store:
|
||||
```bash
|
||||
python3 SKILL_DIR/scripts/evidence-store.py --store evidence.json list
|
||||
python SKILL_DIR/scripts/evidence-store.py --store evidence.json list
|
||||
```
|
||||
3. Copy the forensic report template:
|
||||
```bash
|
||||
|
|
@ -144,7 +144,7 @@ git log --all --diff-filter=A --name-only --format="%H %ai" -- "*.so" "*.dll" "*
|
|||
git log --show-signature --format="%H %ai %aN" > ../signature_check.txt 2>&1
|
||||
```
|
||||
|
||||
**Evidence to collect** (add via `python3 SKILL_DIR/scripts/evidence-store.py add`):
|
||||
**Evidence to collect** (add via `python SKILL_DIR/scripts/evidence-store.py add`):
|
||||
- Each dangling commit SHA → type: `git`
|
||||
- Force-push evidence (reflog showing history rewrite) → type: `git`
|
||||
- Unsigned commits from verified contributors → type: `git`
|
||||
|
|
@ -297,7 +297,7 @@ LIMIT 200
|
|||
|
||||
After all investigators complete:
|
||||
|
||||
1. Run `python3 SKILL_DIR/scripts/evidence-store.py --store evidence.json list` to see all collected evidence.
|
||||
1. Run `python SKILL_DIR/scripts/evidence-store.py --store evidence.json list` to see all collected evidence.
|
||||
2. For each piece of evidence, verify the `content_sha256` hash matches the original source.
|
||||
3. Group evidence by:
|
||||
- **Timeline**: Sort all timestamped evidence chronologically
|
||||
|
|
@ -368,7 +368,7 @@ Populate `investigation-report.md` using the template in [forensic-report.md](./
|
|||
|
||||
## Phase 7: Completion
|
||||
|
||||
1. Run final evidence count: `python3 SKILL_DIR/scripts/evidence-store.py --store evidence.json list`
|
||||
1. Run final evidence count: `python SKILL_DIR/scripts/evidence-store.py --store evidence.json list`
|
||||
2. Archive the full investigation directory.
|
||||
3. If compromise is confirmed:
|
||||
- List immediate mitigations (rotate credentials, pin dependency hashes, notify affected users)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ author: SHL0MS (github.com/SHL0MS)
|
|||
license: MIT
|
||||
platforms: [linux, macos, windows]
|
||||
prerequisites:
|
||||
commands: [python3]
|
||||
commands: [python]
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [privacy, data-broker, opt-out, ccpa, gdpr, security, doxxing]
|
||||
|
|
@ -61,7 +61,7 @@ verifying re-scan.
|
|||
|
||||
## Prerequisites
|
||||
|
||||
- `python3` (stdlib only; no extra packages needed for the core engine).
|
||||
- `python` (stdlib only; no extra packages needed for the core engine).
|
||||
- **Optional upgrades** (the skill works zero-config without these; `setup --auto` turns on every
|
||||
one it detects, reading credentials from the shell env **and from `$HERMES_HOME/.env`** so keys
|
||||
Hermes already loads for its own tools are picked up without re-exporting - each one converts a
|
||||
|
|
@ -98,7 +98,7 @@ verifying re-scan.
|
|||
Run everything through the `terminal` tool. From this skill's directory:
|
||||
|
||||
```bash
|
||||
PDD="python3 scripts/pdd.py"
|
||||
PDD="python scripts/pdd.py"
|
||||
```
|
||||
|
||||
The engine stores data under `$PDD_DATA_DIR` (default `$HERMES_HOME/unbroker`), written
|
||||
|
|
@ -311,7 +311,7 @@ recording `found` and before any deletion.
|
|||
## Verification
|
||||
|
||||
- `scripts/run_tests.sh tests/skills/test_unbroker_skill.py` (hermetic; no network), or the
|
||||
dependency-free runner `python3 tests/skills/test_unbroker_skill.py`.
|
||||
dependency-free runner `python tests/skills/test_unbroker_skill.py`.
|
||||
- Dry run: `$PDD setup --auto && $PDD doctor && SID=$($PDD intake --full-name "Test Person"
|
||||
--email t@example.com --consent | python3 -c 'import sys,json;print(json.load(sys.stdin)["subject_id"])')
|
||||
--email t@example.com --consent | python -c 'import sys,json;print(json.load(sys.stdin)["subject_id"])')
|
||||
&& $PDD next "$SID"` and confirm a readiness summary plus an ordered action queue.
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ A single-file Python 3 stdlib wrapper. Same on every OS. The agent's default ent
|
|||
### `search` — find all matches of a pattern
|
||||
|
||||
```bash
|
||||
python3 scripts/ast_grep_helper.py search 'console.log($MSG)' --lang ts src/
|
||||
python scripts/ast_grep_helper.py search 'console.log($MSG)' --lang ts src/
|
||||
```
|
||||
|
||||
Validates the pattern offline first. If the pattern looks like regex (`\w`, `.*`, `|`, etc.) the helper exits with a hint and never calls `sg` — saves a round-trip. Pass `--force` to skip validation.
|
||||
|
|
@ -98,10 +98,10 @@ Flags:
|
|||
|
||||
```bash
|
||||
# Dry-run preview (default — no files mutated)
|
||||
python3 scripts/ast_grep_helper.py replace 'console.log($MSG)' 'logger.info($MSG)' --lang ts src/
|
||||
python scripts/ast_grep_helper.py replace 'console.log($MSG)' 'logger.info($MSG)' --lang ts src/
|
||||
|
||||
# Actually apply
|
||||
python3 scripts/ast_grep_helper.py replace 'console.log($MSG)' 'logger.info($MSG)' --lang ts src/ --apply
|
||||
python scripts/ast_grep_helper.py replace 'console.log($MSG)' 'logger.info($MSG)' --lang ts src/ --apply
|
||||
```
|
||||
|
||||
The helper:
|
||||
|
|
@ -113,16 +113,16 @@ The helper:
|
|||
|
||||
```bash
|
||||
# Discover sgconfig.yml from cwd and run all rules
|
||||
python3 scripts/ast_grep_helper.py scan src/
|
||||
python scripts/ast_grep_helper.py scan src/
|
||||
|
||||
# Run a single rule file
|
||||
python3 scripts/ast_grep_helper.py scan -r rules/no-console.yml src/
|
||||
python scripts/ast_grep_helper.py scan -r rules/no-console.yml src/
|
||||
|
||||
# Apply auto-fixes
|
||||
python3 scripts/ast_grep_helper.py scan -U src/
|
||||
python scripts/ast_grep_helper.py scan -U src/
|
||||
|
||||
# CI-friendly GitHub annotations
|
||||
python3 scripts/ast_grep_helper.py scan --report-style short src/
|
||||
python scripts/ast_grep_helper.py scan --report-style short src/
|
||||
```
|
||||
|
||||
### `validate` — offline pattern check (no `sg` call)
|
||||
|
|
@ -130,19 +130,19 @@ python3 scripts/ast_grep_helper.py scan --report-style short src/
|
|||
Useful for CI lints, pre-commit hooks, and quick sanity checks:
|
||||
|
||||
```bash
|
||||
python3 scripts/ast_grep_helper.py validate '\w+' --lang ts
|
||||
python scripts/ast_grep_helper.py validate '\w+' --lang ts
|
||||
# → exit 2: regex \w not supported. Use $VAR for identifiers.
|
||||
|
||||
python3 scripts/ast_grep_helper.py validate 'console.log($MSG)' --lang ts
|
||||
python scripts/ast_grep_helper.py validate 'console.log($MSG)' --lang ts
|
||||
# → exit 0: pattern looks plausible for ast-grep.
|
||||
```
|
||||
|
||||
### `langs` / `doctor` / `install`
|
||||
|
||||
```bash
|
||||
python3 scripts/ast_grep_helper.py langs # list 25 supported languages and aliases
|
||||
python3 scripts/ast_grep_helper.py doctor # check ast-grep binary availability
|
||||
python3 scripts/ast_grep_helper.py install # delegate to install.sh / install.ps1
|
||||
python scripts/ast_grep_helper.py langs # list 25 supported languages and aliases
|
||||
python scripts/ast_grep_helper.py doctor # check ast-grep binary availability
|
||||
python scripts/ast_grep_helper.py install # delegate to install.sh / install.ps1
|
||||
```
|
||||
|
||||
`new` and `test` subcommands proxy directly to `sg new` and `sg test`.
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ terminal("""curl -X POST https://api.example.com/users \\
|
|||
terminal('curl -sI https://api.example.com/health')
|
||||
|
||||
# Pretty-print JSON
|
||||
terminal('curl -s https://api.example.com/users | python3 -m json.tool')
|
||||
terminal('curl -s https://api.example.com/users | python -m json.tool')
|
||||
```
|
||||
|
||||
### GraphQL via terminal
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ Use the `terminal` tool for every step. Always pin the version (`wrangler@latest
|
|||
|
||||
3. **Parse the URLs** from that output. Run the helper to extract them reliably instead of eyeballing:
|
||||
```
|
||||
npx wrangler@latest deploy --temporary 2>&1 | python3 scripts/parse_deploy_output.py
|
||||
npx wrangler@latest deploy --temporary 2>&1 | python scripts/parse_deploy_output.py
|
||||
```
|
||||
(Resolve `scripts/parse_deploy_output.py` to this skill's absolute path.) It prints JSON: `{"live_url", "claim_url", "account", "account_state", "expires_minutes", "deployed"}`.
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ Use the `terminal` tool for every step. Always pin the version (`wrangler@latest
|
|||
|---|---|
|
||||
| Check version (need 4.102.0+) | `npx wrangler@latest --version` |
|
||||
| Deploy (no account) | `npx wrangler@latest deploy --temporary` |
|
||||
| Deploy + parse URLs | `npx wrangler@latest deploy --temporary 2>&1 \| python3 scripts/parse_deploy_output.py` |
|
||||
| Deploy + parse URLs | `npx wrangler@latest deploy --temporary 2>&1 \| python scripts/parse_deploy_output.py` |
|
||||
| Verify live | `curl -sS <live_url>` |
|
||||
| Clear cached temp account | `npx wrangler@latest logout` |
|
||||
|
||||
|
|
@ -124,4 +124,4 @@ Use the `terminal` tool for every step. Always pin the version (`wrangler@latest
|
|||
- `npx wrangler@latest deploy --temporary` prints a `workers.dev` live URL and a `claim-preview?claimToken=` claim URL.
|
||||
- `curl -sS <live_url>` returns the exact body the Worker code produces.
|
||||
- A second deploy reports `Account: <name> (reused)` and the live URL is unchanged.
|
||||
- The parser script's self-test passes: `python3 scripts/parse_deploy_output.py --selftest`.
|
||||
- The parser script's self-test passes: `python scripts/parse_deploy_output.py --selftest`.
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ Parse `structured_output` from the JSON result. Claude validates output against
|
|||
terminal(command="claude -p 'Start refactoring the database layer' --output-format json --max-turns 10 > /tmp/session.json", workdir="/project", timeout=180)
|
||||
|
||||
# Resume with session ID
|
||||
terminal(command="claude -p 'Continue and add connection pooling' --resume $(cat /tmp/session.json | python3 -c 'import json,sys; print(json.load(sys.stdin)[\"session_id\"])') --max-turns 5", workdir="/project", timeout=120)
|
||||
terminal(command="claude -p 'Continue and add connection pooling' --resume $(cat /tmp/session.json | python -c 'import json,sys; print(json.load(sys.stdin)[\"session_id\"])') --max-turns 5", workdir="/project", timeout=120)
|
||||
|
||||
# Or resume the most recent session in the same directory
|
||||
terminal(command="claude -p 'What did you do last time?' --continue --max-turns 1", workdir="/project", timeout=30)
|
||||
|
|
@ -722,7 +722,7 @@ Use `/context` in interactive mode to see a colored grid of context usage. Key t
|
|||
2. **`--dangerously-skip-permissions` dialog defaults to "No, exit"** — you must send Down then Enter to accept. Print mode (`-p`) skips this entirely.
|
||||
3. **`--max-budget-usd` minimum is ~$0.05** — system prompt cache creation alone costs this much. Setting lower will error immediately.
|
||||
4. **`--max-turns` is print-mode only** — ignored in interactive sessions.
|
||||
5. **Claude may use `python` instead of `python3`** — on systems without a `python` symlink, Claude's bash commands will fail on first try but it self-corrects.
|
||||
5. **Claude may use `python` instead of `python`** — on systems without a `python` symlink, Claude's bash commands will fail on first try but it self-corrects.
|
||||
6. **Session resumption requires same directory** — `--continue` finds the most recent session for the current working directory.
|
||||
7. **`--json-schema` needs enough `--max-turns`** — Claude must read files before producing structured output, which takes multiple turns.
|
||||
8. **Trust dialog only appears once per directory** — first-time only, then cached.
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@ pip install pyfiglet --break-system-packages -q
|
|||
### Usage
|
||||
|
||||
```bash
|
||||
python3 -m pyfiglet "YOUR TEXT" -f slant
|
||||
python3 -m pyfiglet "TEXT" -f doom -w 80 # Set width
|
||||
python3 -m pyfiglet --list_fonts # List all 571 fonts
|
||||
python -m pyfiglet "YOUR TEXT" -f slant
|
||||
python -m pyfiglet "TEXT" -f doom -w 80 # Set width
|
||||
python -m pyfiglet --list_fonts # List all 571 fonts
|
||||
```
|
||||
|
||||
### Recommended fonts
|
||||
|
|
@ -156,7 +156,7 @@ boxes -l # List all 70+ designs
|
|||
### Combine with pyfiglet or asciified
|
||||
|
||||
```bash
|
||||
python3 -m pyfiglet "HERMES" -f slant | boxes -d stone
|
||||
python -m pyfiglet "HERMES" -f slant | boxes -d stone
|
||||
# Or without pyfiglet installed:
|
||||
curl -s "https://asciified.thelicato.io/api/v2/ascii?text=HERMES&font=Slant" | boxes -d stone
|
||||
```
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ license: MIT
|
|||
platforms: [macos, linux, windows]
|
||||
compatibility: "Requires ComfyUI (local, Comfy Desktop, or Comfy Cloud) and comfy-cli (auto-installed via pipx/uvx by the setup script)."
|
||||
prerequisites:
|
||||
commands: ["python3"]
|
||||
commands: ["python"]
|
||||
setup:
|
||||
help: "Run scripts/hardware_check.py FIRST to decide local vs Comfy Cloud; then scripts/comfyui_setup.sh auto-installs locally (or use Cloud API key for platform.comfy.org)."
|
||||
metadata:
|
||||
|
|
@ -108,7 +108,7 @@ command -v comfy >/dev/null 2>&1 && echo "comfy-cli: installed"
|
|||
curl -s http://127.0.0.1:8188/system_stats 2>/dev/null && echo "server: running"
|
||||
|
||||
# Can this machine run ComfyUI locally? (GPU/VRAM/disk check)
|
||||
python3 scripts/hardware_check.py
|
||||
python scripts/hardware_check.py
|
||||
```
|
||||
|
||||
If nothing is installed, see **Setup & Onboarding** below — but always run the
|
||||
|
|
@ -117,7 +117,7 @@ hardware check first.
|
|||
### One-line health check
|
||||
|
||||
```bash
|
||||
python3 scripts/health_check.py
|
||||
python scripts/health_check.py
|
||||
# → JSON: comfy_cli on PATH? server reachable? at least one checkpoint? smoke-test passes?
|
||||
```
|
||||
|
||||
|
|
@ -139,10 +139,10 @@ executable**. The scripts detect this and tell you to re-export.
|
|||
### Step 2: See what's controllable
|
||||
|
||||
```bash
|
||||
python3 scripts/extract_schema.py workflow_api.json --summary-only
|
||||
python scripts/extract_schema.py workflow_api.json --summary-only
|
||||
# → {"parameter_count": 12, "has_negative_prompt": true, "has_seed": true, ...}
|
||||
|
||||
python3 scripts/extract_schema.py workflow_api.json
|
||||
python scripts/extract_schema.py workflow_api.json
|
||||
# → full schema with parameters, model deps, embedding refs
|
||||
```
|
||||
|
||||
|
|
@ -150,33 +150,33 @@ python3 scripts/extract_schema.py workflow_api.json
|
|||
|
||||
```bash
|
||||
# Local (defaults to http://127.0.0.1:8188)
|
||||
python3 scripts/run_workflow.py \
|
||||
python scripts/run_workflow.py \
|
||||
--workflow workflow_api.json \
|
||||
--args '{"prompt": "a beautiful sunset over mountains", "seed": -1, "steps": 30}' \
|
||||
--output-dir ./outputs
|
||||
|
||||
# Cloud (export API key once; uses correct /api routing automatically)
|
||||
export COMFY_CLOUD_API_KEY="comfyui-..."
|
||||
python3 scripts/run_workflow.py \
|
||||
python scripts/run_workflow.py \
|
||||
--workflow workflow_api.json \
|
||||
--args '{"prompt": "..."}' \
|
||||
--host https://cloud.comfy.org \
|
||||
--output-dir ./outputs
|
||||
|
||||
# Real-time progress via WebSocket (requires `pip install websocket-client`)
|
||||
python3 scripts/run_workflow.py \
|
||||
python scripts/run_workflow.py \
|
||||
--workflow flux_dev.json \
|
||||
--args '{"prompt": "..."}' \
|
||||
--ws
|
||||
|
||||
# img2img / inpaint: pass --input-image to upload + reference automatically
|
||||
python3 scripts/run_workflow.py \
|
||||
python scripts/run_workflow.py \
|
||||
--workflow sdxl_img2img.json \
|
||||
--input-image image=./photo.png \
|
||||
--args '{"prompt": "make it watercolor", "denoise": 0.6}'
|
||||
|
||||
# Batch / sweep: 8 random seeds, parallel up to cloud tier limit
|
||||
python3 scripts/run_batch.py \
|
||||
python scripts/run_batch.py \
|
||||
--workflow sdxl.json \
|
||||
--args '{"prompt": "abstract"}' \
|
||||
--count 8 --randomize-seed --parallel 3 \
|
||||
|
|
@ -266,9 +266,9 @@ Routing:
|
|||
### Step 1: Verify Hardware (ONLY if user chose local)
|
||||
|
||||
```bash
|
||||
python3 scripts/hardware_check.py --json
|
||||
python scripts/hardware_check.py --json
|
||||
# Optional: also probe `torch` for actual CUDA/MPS:
|
||||
python3 scripts/hardware_check.py --json --check-pytorch
|
||||
python scripts/hardware_check.py --json --check-pytorch
|
||||
```
|
||||
|
||||
| Verdict | Meaning | Action |
|
||||
|
|
@ -328,7 +328,7 @@ For users without a capable GPU or who want zero setup. Hosted on RTX 6000 Pro.
|
|||
```
|
||||
4. Run workflows:
|
||||
```bash
|
||||
python3 scripts/run_workflow.py \
|
||||
python scripts/run_workflow.py \
|
||||
--workflow workflows/flux_dev_txt2img.json \
|
||||
--args '{"prompt": "..."}' \
|
||||
--host https://cloud.comfy.org \
|
||||
|
|
@ -465,13 +465,13 @@ comfy node install-deps --workflow=workflow.json # install everything a workfl
|
|||
### Post-Install: Verify
|
||||
|
||||
```bash
|
||||
python3 scripts/health_check.py
|
||||
python scripts/health_check.py
|
||||
# → comfy_cli on PATH? server reachable? checkpoints? smoke test?
|
||||
|
||||
python3 scripts/check_deps.py my_workflow.json
|
||||
python scripts/check_deps.py my_workflow.json
|
||||
# → are this workflow's nodes/models/embeddings installed?
|
||||
|
||||
python3 scripts/run_workflow.py \
|
||||
python scripts/run_workflow.py \
|
||||
--workflow workflows/sd15_txt2img.json \
|
||||
--args '{"prompt": "test", "steps": 4}' \
|
||||
--output-dir ./test-outputs
|
||||
|
|
@ -482,7 +482,7 @@ python3 scripts/run_workflow.py \
|
|||
The simplest way is to use `--input-image` with `run_workflow.py`:
|
||||
|
||||
```bash
|
||||
python3 scripts/run_workflow.py \
|
||||
python scripts/run_workflow.py \
|
||||
--workflow workflows/sdxl_img2img.json \
|
||||
--input-image image=./photo.png \
|
||||
--args '{"prompt": "make it cyberpunk", "denoise": 0.6}'
|
||||
|
|
@ -492,7 +492,7 @@ The flag uploads `photo.png`, then injects its server-side filename into
|
|||
whatever schema parameter is named `image`. For inpainting, pass both:
|
||||
|
||||
```bash
|
||||
python3 scripts/run_workflow.py \
|
||||
python scripts/run_workflow.py \
|
||||
--workflow workflows/sdxl_inpaint.json \
|
||||
--input-image image=./photo.png \
|
||||
--input-image mask_image=./mask.png \
|
||||
|
|
@ -536,7 +536,7 @@ curl -X POST "https://cloud.comfy.org/api/upload/image" \
|
|||
|
||||
```bash
|
||||
# Local
|
||||
curl -s http://127.0.0.1:8188/queue | python3 -m json.tool
|
||||
curl -s http://127.0.0.1:8188/queue | python -m json.tool
|
||||
curl -X POST http://127.0.0.1:8188/queue -d '{"clear": true}' # cancel pending
|
||||
curl -X POST http://127.0.0.1:8188/interrupt # cancel running
|
||||
curl -X POST http://127.0.0.1:8188/free \
|
||||
|
|
@ -544,7 +544,7 @@ curl -X POST http://127.0.0.1:8188/free \
|
|||
-d '{"unload_models": true, "free_memory": true}'
|
||||
|
||||
# Cloud — same paths under /api/, plus:
|
||||
python3 scripts/fetch_logs.py --tail-queue --host https://cloud.comfy.org
|
||||
python scripts/fetch_logs.py --tail-queue --host https://cloud.comfy.org
|
||||
```
|
||||
|
||||
## Pitfalls
|
||||
|
|
@ -599,7 +599,7 @@ python3 scripts/fetch_logs.py --tail-queue --host https://cloud.comfy.org
|
|||
|
||||
## Verification Checklist
|
||||
|
||||
Use `python3 scripts/health_check.py` to run the whole list at once. Manual:
|
||||
Use `python scripts/health_check.py` to run the whole list at once. Manual:
|
||||
|
||||
- [ ] `hardware_check.py` verdict is `ok` OR the user explicitly chose Comfy Cloud
|
||||
- [ ] `comfy --version` works (or `uvx --from comfy-cli comfy --help`)
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ Key implementation patterns:
|
|||
### Step 4: Preview & Iterate
|
||||
|
||||
- Open HTML file directly in browser — no server needed for basic sketches
|
||||
- For `loadImage()`/`loadFont()` from local files: use `scripts/serve.sh` or `python3 -m http.server`
|
||||
- For `loadImage()`/`loadFont()` from local files: use `scripts/serve.sh` or `python -m http.server`
|
||||
- Chrome DevTools Performance tab to verify 60fps
|
||||
- Test at target export resolution, not just the window size
|
||||
- Adjust parameters until the visual matches the concept from Step 1
|
||||
|
|
@ -488,7 +488,7 @@ When building p5.js sketches:
|
|||
|
||||
1. **Write the HTML file** — single self-contained file, all code inline
|
||||
2. **Open in browser** — `open sketch.html` (macOS) or `xdg-open sketch.html` (Linux)
|
||||
3. **Local assets** (fonts, images) require a server: `python3 -m http.server 8080` in the project directory, then open `http://localhost:8080/sketch.html`
|
||||
3. **Local assets** (fonts, images) require a server: `python -m http.server 8080` in the project directory, then open `http://localhost:8080/sketch.html`
|
||||
4. **Export PNG/GIF** — add `keyPressed()` shortcuts as shown above, tell the user which key to press
|
||||
5. **Headless export** — `node scripts/export-frames.js sketch.html --frames 300` for automated frame capture (sketch must use `noLoop()` + `_p5Ready`)
|
||||
6. **MP4 rendering** — `bash scripts/render.sh sketch.html output.mp4 --duration 30`
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ See `templates/donut-orbit.html` and `templates/hello-orb-flow.html` for working
|
|||
4. **Tune the aesthetic** — font, palette, composition, interaction. This is the work; don't skip it.
|
||||
5. **Verify locally**:
|
||||
```sh
|
||||
cd <dir-with-html> && python3 -m http.server 8765
|
||||
cd <dir-with-html> && python -m http.server 8765
|
||||
# then open http://localhost:8765/<file>.html
|
||||
```
|
||||
6. **Check the console** — pretext will throw if `prepareWithSegments` is called with a bad font string; `Intl.Segmenter` is available in every modern browser.
|
||||
|
|
@ -194,14 +194,14 @@ See `templates/donut-orbit.html` and `templates/hello-orb-flow.html` for working
|
|||
|
||||
## Verification Checklist
|
||||
|
||||
- [ ] Demo is a single self-contained `.html` file — opens by double-click or `python3 -m http.server`
|
||||
- [ ] Demo is a single self-contained `.html` file — opens by double-click or `python -m http.server`
|
||||
- [ ] `@chenglou/pretext` imported via `esm.sh` with pinned version
|
||||
- [ ] Corpus is real prose, not lorem ipsum, and matches the demo's concept
|
||||
- [ ] Font string passed to `prepare` matches the CSS font exactly
|
||||
- [ ] `prepare()` / `prepareWithSegments()` called once, not per frame
|
||||
- [ ] Dark background + considered palette — not the default white canvas
|
||||
- [ ] At least one interactive response (drag / hover / scroll / click) or idle auto-motion
|
||||
- [ ] Tested locally with `python3 -m http.server` and confirmed no console errors
|
||||
- [ ] Tested locally with `python -m http.server` and confirmed no console errors
|
||||
- [ ] 60fps on a mid-tier laptop (or graceful degradation documented)
|
||||
- [ ] One "extra mile" detail the user didn't ask for
|
||||
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ If git credentials are already configured (via credential.helper store), the tok
|
|||
|
||||
```bash
|
||||
# Read from git credential store
|
||||
uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py"
|
||||
uv run python "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py"
|
||||
```
|
||||
|
||||
### Helper: Detect Auth Method
|
||||
|
|
@ -265,7 +265,7 @@ elif _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] &&
|
|||
export GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
echo "AUTH_METHOD=curl"
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
export GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
export GITHUB_TOKEN=$(uv run python "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
echo "AUTH_METHOD=curl"
|
||||
else
|
||||
echo "AUTH_METHOD=none"
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ else
|
|||
if _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] && grep -q "^GITHUB_TOKEN=" "$_hermes_env"; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
GITHUB_TOKEN=$(uv run python "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
@ -144,7 +144,7 @@ PR_NUMBER=123
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
pr = json.load(sys.stdin)
|
||||
print(f\"Title: {pr['title']}\")
|
||||
|
|
@ -157,7 +157,7 @@ print(f\"Body:\n{pr['body']}\")"
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER/files \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
for f in json.load(sys.stdin):
|
||||
print(f\"{f['status']:10} +{f['additions']:-4} -{f['deletions']:-4} {f['filename']}\")"
|
||||
|
|
@ -224,7 +224,7 @@ gh api repos/$OWNER/$REPO/pulls/123/comments \
|
|||
HEAD_SHA=$(curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER \
|
||||
| python3 -c "import sys,json; print(json.load(sys.stdin)['head']['sha'])")
|
||||
| python -c "import sys,json; print(json.load(sys.stdin)['head']['sha'])")
|
||||
|
||||
curl -s -X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
|
|
@ -254,7 +254,7 @@ gh pr review 123 --comment --body "Some suggestions, nothing blocking."
|
|||
HEAD_SHA=$(curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER \
|
||||
| python3 -c "import sys,json; print(json.load(sys.stdin)['head']['sha'])")
|
||||
| python -c "import sys,json; print(json.load(sys.stdin)['head']['sha'])")
|
||||
|
||||
curl -s -X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
|
|
@ -419,7 +419,7 @@ gh pr review $PR_NUMBER --request-changes --body "Found a few issues — see inl
|
|||
```bash
|
||||
HEAD_SHA=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$GH_OWNER/$GH_REPO/pulls/$PR_NUMBER \
|
||||
| python3 -c "import sys,json; print(json.load(sys.stdin)['head']['sha'])")
|
||||
| python -c "import sys,json; print(json.load(sys.stdin)['head']['sha'])")
|
||||
|
||||
# Build the review JSON — event is APPROVE, REQUEST_CHANGES, or COMMENT
|
||||
curl -s -X POST \
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ else
|
|||
if _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] && grep -q "^GITHUB_TOKEN=" "$_hermes_env"; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
GITHUB_TOKEN=$(uv run python "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
@ -63,7 +63,7 @@ gh issue view 42
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
"https://api.github.com/repos/$OWNER/$REPO/issues?state=open&per_page=20" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
for i in json.load(sys.stdin):
|
||||
if 'pull_request' not in i: # GitHub API returns PRs in /issues too
|
||||
|
|
@ -74,7 +74,7 @@ for i in json.load(sys.stdin):
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
"https://api.github.com/repos/$OWNER/$REPO/issues?state=open&labels=bug&per_page=20" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
for i in json.load(sys.stdin):
|
||||
if 'pull_request' not in i:
|
||||
|
|
@ -84,7 +84,7 @@ for i in json.load(sys.stdin):
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$OWNER/$REPO/issues/42 \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
i = json.load(sys.stdin)
|
||||
labels = ', '.join(l['name'] for l in i['labels'])
|
||||
|
|
@ -98,7 +98,7 @@ print(f\"\n{i['body']}\")"
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
"https://api.github.com/search/issues?q=authentication+error+repo:$OWNER/$REPO" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
for i in json.load(sys.stdin)['items']:
|
||||
print(f\"#{i['number']} {i['state']:6} {i['title']}\")"
|
||||
|
|
@ -206,7 +206,7 @@ curl -s -X DELETE \
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$OWNER/$REPO/labels \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
for l in json.load(sys.stdin):
|
||||
print(f\" {l['name']:30} {l.get('description', '')}\")"
|
||||
|
|
@ -312,7 +312,7 @@ gh issue list --label "needs-triage" --state open
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
"https://api.github.com/repos/$OWNER/$REPO/issues?labels=needs-triage&state=open" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
for i in json.load(sys.stdin):
|
||||
if 'pull_request' not in i:
|
||||
|
|
@ -346,7 +346,7 @@ gh issue list --label "wontfix" --json number --jq '.[].number' | \
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
"https://api.github.com/repos/$OWNER/$REPO/issues?labels=wontfix&state=open" \
|
||||
| python3 -c "import sys,json; [print(i['number']) for i in json.load(sys.stdin)]" \
|
||||
| python -c "import sys,json; [print(i['number']) for i in json.load(sys.stdin)]" \
|
||||
| while read num; do
|
||||
curl -s -X PATCH \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ else
|
|||
if _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] && grep -q "^GITHUB_TOKEN=" "$_hermes_env"; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
GITHUB_TOKEN=$(uv run python "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
@ -173,7 +173,7 @@ SHA=$(git rev-parse HEAD)
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/status \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
print(f\"Overall: {data['state']}\")
|
||||
|
|
@ -184,7 +184,7 @@ for s in data.get('statuses', []):
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/check-runs \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
for cr in data.get('check_runs', []):
|
||||
|
|
@ -200,7 +200,7 @@ for i in $(seq 1 20); do
|
|||
STATUS=$(curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$OWNER/$REPO/commits/$SHA/status \
|
||||
| python3 -c "import sys,json; print(json.load(sys.stdin)['state'])")
|
||||
| python -c "import sys,json; print(json.load(sys.stdin)['state'])")
|
||||
echo "Check $i: $STATUS"
|
||||
if [ "$STATUS" = "success" ] || [ "$STATUS" = "failure" ] || [ "$STATUS" = "error" ]; then
|
||||
break
|
||||
|
|
@ -234,7 +234,7 @@ BRANCH=$(git branch --show-current)
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
"https://api.github.com/repos/$OWNER/$REPO/actions/runs?branch=$BRANCH&per_page=5" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
runs = json.load(sys.stdin)['workflow_runs']
|
||||
for r in runs:
|
||||
|
|
@ -319,7 +319,7 @@ Merge methods: `"merge"` (merge commit), `"squash"`, `"rebase"`
|
|||
PR_NODE_ID=$(curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$OWNER/$REPO/pulls/$PR_NUMBER \
|
||||
| python3 -c "import sys,json; print(json.load(sys.stdin)['node_id'])")
|
||||
| python -c "import sys,json; print(json.load(sys.stdin)['node_id'])")
|
||||
|
||||
curl -s -X POST \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ else
|
|||
if _hermes_env="${HERMES_HOME:-$HOME/.hermes}/.env"; [ -f "$_hermes_env" ] && grep -q "^GITHUB_TOKEN=" "$_hermes_env"; then
|
||||
GITHUB_TOKEN=$(grep "^GITHUB_TOKEN=" "$_hermes_env" | head -1 | cut -d= -f2 | tr -d '\n\r')
|
||||
elif grep -q "github.com" ~/.git-credentials 2>/dev/null; then
|
||||
GITHUB_TOKEN=$(uv run python3 "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
GITHUB_TOKEN=$(uv run python "${HERMES_HOME:-$HOME/.hermes}/skills/github/github-auth/scripts/git-credential-token.py")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
|
@ -39,7 +39,7 @@ fi
|
|||
if [ "$AUTH" = "gh" ]; then
|
||||
GH_USER=$(gh api user --jq '.login')
|
||||
else
|
||||
GH_USER=$(curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/user | python3 -c "import sys,json; print(json.load(sys.stdin)['login'])")
|
||||
GH_USER=$(curl -s -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/user | python -c "import sys,json; print(json.load(sys.stdin)['login'])")
|
||||
fi
|
||||
```
|
||||
|
||||
|
|
@ -213,7 +213,7 @@ gh search repos "machine learning" --language python --sort stars
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$OWNER/$REPO \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
r = json.load(sys.stdin)
|
||||
print(f\"Name: {r['full_name']}\")
|
||||
|
|
@ -226,7 +226,7 @@ print(f\"Language: {r['language']}\")"
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
"https://api.github.com/user/repos?per_page=20&sort=updated" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
for r in json.load(sys.stdin):
|
||||
vis = 'private' if r['private'] else 'public'
|
||||
|
|
@ -235,7 +235,7 @@ for r in json.load(sys.stdin):
|
|||
# Search repos
|
||||
curl -s \
|
||||
"https://api.github.com/search/repositories?q=machine+learning+language:python&sort=stars&per_page=10" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
for r in json.load(sys.stdin)['items']:
|
||||
print(f\" {r['full_name']:40} ★{r['stargazers_count']:6} {r['description'][:60] if r['description'] else ''}\")"
|
||||
|
|
@ -321,7 +321,7 @@ curl -s \
|
|||
https://api.github.com/repos/$OWNER/$REPO/actions/secrets/public-key
|
||||
|
||||
# Encrypt and set (requires Python with PyNaCl)
|
||||
python3 -c "
|
||||
python -c "
|
||||
from base64 import b64encode
|
||||
from nacl import encoding, public
|
||||
import json, sys
|
||||
|
|
@ -349,7 +349,7 @@ curl -s -X PUT \
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$OWNER/$REPO/actions/secrets \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
for s in json.load(sys.stdin)['secrets']:
|
||||
print(f\" {s['name']:30} updated: {s['updated_at']}\")"
|
||||
|
|
@ -389,7 +389,7 @@ curl -s -X POST \
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$OWNER/$REPO/releases \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
for r in json.load(sys.stdin):
|
||||
tag = r.get('tag_name', 'no tag')
|
||||
|
|
@ -426,7 +426,7 @@ gh workflow run deploy.yml -f environment=staging
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/repos/$OWNER/$REPO/actions/workflows \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
for w in json.load(sys.stdin)['workflows']:
|
||||
print(f\" {w['id']:10} {w['name']:30} {w['state']}\")"
|
||||
|
|
@ -435,7 +435,7 @@ for w in json.load(sys.stdin)['workflows']:
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
"https://api.github.com/repos/$OWNER/$REPO/actions/runs?per_page=10" \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
for r in json.load(sys.stdin)['workflow_runs']:
|
||||
print(f\" Run {r['id']} {r['name']:30} {r['conclusion'] or r['status']}\")"
|
||||
|
|
@ -494,7 +494,7 @@ curl -s -X POST \
|
|||
curl -s \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
https://api.github.com/gists \
|
||||
| python3 -c "
|
||||
| python -c "
|
||||
import sys, json
|
||||
for g in json.load(sys.stdin):
|
||||
files = ', '.join(g['files'].keys())
|
||||
|
|
|
|||
|
|
@ -34,16 +34,16 @@ uv pip install youtube-transcript-api
|
|||
|
||||
```bash
|
||||
# JSON output with metadata
|
||||
uv run python3 SKILL_DIR/scripts/fetch_transcript.py "https://youtube.com/watch?v=VIDEO_ID"
|
||||
uv run python SKILL_DIR/scripts/fetch_transcript.py "https://youtube.com/watch?v=VIDEO_ID"
|
||||
|
||||
# Plain text (good for piping into further processing)
|
||||
uv run python3 SKILL_DIR/scripts/fetch_transcript.py "URL" --text-only
|
||||
uv run python SKILL_DIR/scripts/fetch_transcript.py "URL" --text-only
|
||||
|
||||
# With timestamps
|
||||
uv run python3 SKILL_DIR/scripts/fetch_transcript.py "URL" --timestamps
|
||||
uv run python SKILL_DIR/scripts/fetch_transcript.py "URL" --timestamps
|
||||
|
||||
# Specific language with fallback chain
|
||||
uv run python3 SKILL_DIR/scripts/fetch_transcript.py "URL" --language tr,en
|
||||
uv run python SKILL_DIR/scripts/fetch_transcript.py "URL" --language tr,en
|
||||
```
|
||||
|
||||
## Output Formats
|
||||
|
|
@ -69,7 +69,7 @@ After fetching the transcript, format it based on what the user asks for:
|
|||
|
||||
## Workflow
|
||||
|
||||
1. **Fetch** the transcript using the helper script with `--text-only --timestamps` via `uv run python3`.
|
||||
1. **Fetch** the transcript using the helper script with `--text-only --timestamps` via `uv run python`.
|
||||
2. **Validate**: confirm the output is non-empty and in the expected language. If empty, retry without `--language` to get any available transcript. If still empty, tell the user the video likely has transcripts disabled.
|
||||
3. **Chunk if needed**: if the transcript exceeds ~50K characters, split into overlapping chunks (~40K with 2K overlap) and summarize each chunk before merging.
|
||||
4. **Transform** into the requested output format. If the user did not specify a format, default to a summary.
|
||||
|
|
|
|||
|
|
@ -44,10 +44,10 @@ Work with Airtable's REST API directly via `curl` using the `terminal` tool. No
|
|||
Base curl pattern:
|
||||
```bash
|
||||
curl -s "https://api.airtable.com/v0/$BASE_ID/$TABLE?maxRecords=5" \
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.tool
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python -m json.tool
|
||||
```
|
||||
|
||||
`-s` suppresses curl's progress bar — keep it set for every call so the tool output stays clean for Hermes. Pipe through `python3 -m json.tool` (always present) or `jq` (if installed) for readable JSON.
|
||||
`-s` suppresses curl's progress bar — keep it set for every call so the tool output stays clean for Hermes. Pipe through `python -m json.tool` (always present) or `jq` (if installed) for readable JSON.
|
||||
|
||||
## Field Types (request body shapes)
|
||||
|
||||
|
|
@ -73,35 +73,35 @@ Pass `"typecast": true` at the top level of a create/update body to let Airtable
|
|||
### List bases the token can see
|
||||
```bash
|
||||
curl -s "https://api.airtable.com/v0/meta/bases" \
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.tool
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python -m json.tool
|
||||
```
|
||||
|
||||
### List tables + schema for a base
|
||||
```bash
|
||||
curl -s "https://api.airtable.com/v0/meta/bases/$BASE_ID/tables" \
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.tool
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python -m json.tool
|
||||
```
|
||||
Use this BEFORE mutating — confirms exact field names and IDs, surfaces `options.choices` for select fields, and shows primary-field names.
|
||||
|
||||
### List records (first 10)
|
||||
```bash
|
||||
curl -s "https://api.airtable.com/v0/$BASE_ID/$TABLE?maxRecords=10" \
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.tool
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python -m json.tool
|
||||
```
|
||||
|
||||
### Get a single record
|
||||
```bash
|
||||
curl -s "https://api.airtable.com/v0/$BASE_ID/$TABLE/$RECORD_ID" \
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.tool
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python -m json.tool
|
||||
```
|
||||
|
||||
### Filter records (filterByFormula)
|
||||
Airtable formulas must be URL-encoded. Let Python stdlib do it — never hand-encode:
|
||||
```bash
|
||||
FORMULA="{Status}='Todo'"
|
||||
ENC=$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$FORMULA")
|
||||
ENC=$(python -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$FORMULA")
|
||||
curl -s "https://api.airtable.com/v0/$BASE_ID/$TABLE?filterByFormula=$ENC&maxRecords=20" \
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.tool
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python -m json.tool
|
||||
```
|
||||
|
||||
Useful formula patterns:
|
||||
|
|
@ -115,14 +115,14 @@ Useful formula patterns:
|
|||
### Sort + select specific fields
|
||||
```bash
|
||||
curl -s "https://api.airtable.com/v0/$BASE_ID/$TABLE?sort%5B0%5D%5Bfield%5D=Priority&sort%5B0%5D%5Bdirection%5D=asc&fields%5B%5D=Name&fields%5B%5D=Status" \
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.tool
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python -m json.tool
|
||||
```
|
||||
Square brackets in query params MUST be URL-encoded (`%5B` / `%5D`).
|
||||
|
||||
### Use a named view
|
||||
```bash
|
||||
curl -s "https://api.airtable.com/v0/$BASE_ID/$TABLE?view=Grid%20view&maxRecords=50" \
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.tool
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python -m json.tool
|
||||
```
|
||||
Views apply their saved filter + sort server-side.
|
||||
|
||||
|
|
@ -133,7 +133,7 @@ Views apply their saved filter + sort server-side.
|
|||
curl -s -X POST "https://api.airtable.com/v0/$BASE_ID/$TABLE" \
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"fields":{"Name":"New task","Status":"Todo","Priority":"High"}}' | python3 -m json.tool
|
||||
-d '{"fields":{"Name":"New task","Status":"Todo","Priority":"High"}}' | python -m json.tool
|
||||
```
|
||||
|
||||
### Create up to 10 records in one call
|
||||
|
|
@ -147,7 +147,7 @@ curl -s -X POST "https://api.airtable.com/v0/$BASE_ID/$TABLE" \
|
|||
{"fields": {"Name": "Task A", "Status": "Todo"}},
|
||||
{"fields": {"Name": "Task B", "Status": "In progress"}}
|
||||
]
|
||||
}' | python3 -m json.tool
|
||||
}' | python -m json.tool
|
||||
```
|
||||
Batch endpoints are capped at **10 records per request**. For larger inserts, loop in batches of 10 with a short sleep to respect 5 req/sec/base.
|
||||
|
||||
|
|
@ -156,7 +156,7 @@ Batch endpoints are capped at **10 records per request**. For larger inserts, lo
|
|||
curl -s -X PATCH "https://api.airtable.com/v0/$BASE_ID/$TABLE/$RECORD_ID" \
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"fields":{"Status":"Done"}}' | python3 -m json.tool
|
||||
-d '{"fields":{"Status":"Done"}}' | python -m json.tool
|
||||
```
|
||||
|
||||
### Upsert by a merge field (no ID needed)
|
||||
|
|
@ -169,20 +169,20 @@ curl -s -X PATCH "https://api.airtable.com/v0/$BASE_ID/$TABLE" \
|
|||
"records": [
|
||||
{"fields": {"Email": "user@example.com", "Status": "Active"}}
|
||||
]
|
||||
}' | python3 -m json.tool
|
||||
}' | python -m json.tool
|
||||
```
|
||||
`performUpsert` creates records whose merge-field values are new, patches records whose merge-field values already exist. Great for idempotent syncs.
|
||||
|
||||
### Delete a record
|
||||
```bash
|
||||
curl -s -X DELETE "https://api.airtable.com/v0/$BASE_ID/$TABLE/$RECORD_ID" \
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.tool
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python -m json.tool
|
||||
```
|
||||
|
||||
### Delete up to 10 records in one call
|
||||
```bash
|
||||
curl -s -X DELETE "https://api.airtable.com/v0/$BASE_ID/$TABLE?records%5B%5D=rec1&records%5B%5D=rec2" \
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python3 -m json.tool
|
||||
-H "Authorization: Bearer $AIRTABLE_API_KEY" | python -m json.tool
|
||||
```
|
||||
|
||||
## Pagination
|
||||
|
|
@ -195,8 +195,8 @@ while :; do
|
|||
URL="https://api.airtable.com/v0/$BASE_ID/$TABLE?pageSize=100"
|
||||
[ -n "$OFFSET" ] && URL="$URL&offset=$OFFSET"
|
||||
RESP=$(curl -s "$URL" -H "Authorization: Bearer $AIRTABLE_API_KEY")
|
||||
echo "$RESP" | python3 -c 'import json,sys; d=json.load(sys.stdin); [print(r["id"], r["fields"].get("Name","")) for r in d["records"]]'
|
||||
OFFSET=$(echo "$RESP" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("offset",""))')
|
||||
echo "$RESP" | python -c 'import json,sys; d=json.load(sys.stdin); [print(r["id"], r["fields"].get("Name","")) for r in d["records"]]'
|
||||
OFFSET=$(echo "$RESP" | python -c 'import json,sys; d=json.load(sys.stdin); print(d.get("offset",""))')
|
||||
[ -z "$OFFSET" ] && break
|
||||
done
|
||||
```
|
||||
|
|
@ -223,7 +223,7 @@ done
|
|||
|
||||
- **Always use the `terminal` tool with `curl`.** Do NOT use `web_extract` (it can't send auth headers) or `browser_navigate` (needs UI auth and is slow).
|
||||
- **`AIRTABLE_API_KEY` flows from `${HERMES_HOME:-~/.hermes}/.env` into the subprocess automatically** when this skill is loaded — no need to re-export it before each `curl` call.
|
||||
- **Escape curly braces in formulas carefully.** In a heredoc body, `{Status}` is literal. In a shell argument, `{Status}` is safe outside `{...}` brace-expansion context — but pass dynamic strings through `python3 urllib.parse.quote` before splicing into a URL.
|
||||
- **Pretty-print with `python3 -m json.tool`** (always present) rather than `jq` (optional). Only reach for `jq` when you need filtering/projection.
|
||||
- **Escape curly braces in formulas carefully.** In a heredoc body, `{Status}` is literal. In a shell argument, `{Status}` is safe outside `{...}` brace-expansion context — but pass dynamic strings through `python urllib.parse.quote` before splicing into a URL.
|
||||
- **Pretty-print with `python -m json.tool`** (always present) rather than `jq` (optional). Only reach for `jq` when you need filtering/projection.
|
||||
- **Pagination is per-page, not global.** Airtable's 100-record cap is a hard limit; there is no way to bump it. Loop with `offset` until the field is absent.
|
||||
- **Read the `errors` array** on non-2xx responses — Airtable returns structured error codes like `AUTHENTICATION_REQUIRED`, `INVALID_PERMISSIONS`, `MODEL_ID_NOT_FOUND`, `INVALID_MULTIPLE_CHOICE_OPTIONS` that tell you exactly what's wrong.
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ MAPS=~/.hermes/skills/maps/scripts/maps_client.py
|
|||
### search — Geocode a place name
|
||||
|
||||
```bash
|
||||
python3 $MAPS search "Eiffel Tower"
|
||||
python3 $MAPS search "1600 Pennsylvania Ave, Washington DC"
|
||||
python $MAPS search "Eiffel Tower"
|
||||
python $MAPS search "1600 Pennsylvania Ave, Washington DC"
|
||||
```
|
||||
|
||||
Returns: lat, lon, display name, type, bounding box, importance score.
|
||||
|
|
@ -59,7 +59,7 @@ Returns: lat, lon, display name, type, bounding box, importance score.
|
|||
### reverse — Coordinates to address
|
||||
|
||||
```bash
|
||||
python3 $MAPS reverse 48.8584 2.2945
|
||||
python $MAPS reverse 48.8584 2.2945
|
||||
```
|
||||
|
||||
Returns: full address breakdown (street, city, state, country, postcode).
|
||||
|
|
@ -68,15 +68,15 @@ Returns: full address breakdown (street, city, state, country, postcode).
|
|||
|
||||
```bash
|
||||
# By coordinates (from a Telegram location pin, for example)
|
||||
python3 $MAPS nearby 48.8584 2.2945 restaurant --limit 10
|
||||
python3 $MAPS nearby 40.7128 -74.0060 hospital --radius 2000
|
||||
python $MAPS nearby 48.8584 2.2945 restaurant --limit 10
|
||||
python $MAPS nearby 40.7128 -74.0060 hospital --radius 2000
|
||||
|
||||
# By address / city / zip / landmark — --near auto-geocodes
|
||||
python3 $MAPS nearby --near "Times Square, New York" --category cafe
|
||||
python3 $MAPS nearby --near "90210" --category pharmacy
|
||||
python $MAPS nearby --near "Times Square, New York" --category cafe
|
||||
python $MAPS nearby --near "90210" --category pharmacy
|
||||
|
||||
# Multiple categories merged into one query
|
||||
python3 $MAPS nearby --near "downtown austin" --category restaurant --category bar --limit 10
|
||||
python $MAPS nearby --near "downtown austin" --category restaurant --category bar --limit 10
|
||||
```
|
||||
|
||||
46 categories: restaurant, cafe, bar, hospital, pharmacy, hotel, guest_house,
|
||||
|
|
@ -95,9 +95,9 @@ directions from the search point), and promoted tags when available —
|
|||
### distance — Travel distance and time
|
||||
|
||||
```bash
|
||||
python3 $MAPS distance "Paris" --to "Lyon"
|
||||
python3 $MAPS distance "New York" --to "Boston" --mode driving
|
||||
python3 $MAPS distance "Big Ben" --to "Tower Bridge" --mode walking
|
||||
python $MAPS distance "Paris" --to "Lyon"
|
||||
python $MAPS distance "New York" --to "Boston" --mode driving
|
||||
python $MAPS distance "Big Ben" --to "Tower Bridge" --mode walking
|
||||
```
|
||||
|
||||
Modes: driving (default), walking, cycling. Returns road distance, duration,
|
||||
|
|
@ -106,8 +106,8 @@ and straight-line distance for comparison.
|
|||
### directions — Turn-by-turn navigation
|
||||
|
||||
```bash
|
||||
python3 $MAPS directions "Eiffel Tower" --to "Louvre Museum" --mode walking
|
||||
python3 $MAPS directions "JFK Airport" --to "Times Square" --mode driving
|
||||
python $MAPS directions "Eiffel Tower" --to "Louvre Museum" --mode walking
|
||||
python $MAPS directions "JFK Airport" --to "Times Square" --mode driving
|
||||
```
|
||||
|
||||
Returns numbered steps with instruction, distance, duration, road name, and
|
||||
|
|
@ -116,8 +116,8 @@ maneuver type (turn, depart, arrive, etc.).
|
|||
### timezone — Timezone for coordinates
|
||||
|
||||
```bash
|
||||
python3 $MAPS timezone 48.8584 2.2945
|
||||
python3 $MAPS timezone 35.6762 139.6503
|
||||
python $MAPS timezone 48.8584 2.2945
|
||||
python $MAPS timezone 35.6762 139.6503
|
||||
```
|
||||
|
||||
Returns timezone name, UTC offset, and current local time.
|
||||
|
|
@ -125,8 +125,8 @@ Returns timezone name, UTC offset, and current local time.
|
|||
### area — Bounding box and area for a place
|
||||
|
||||
```bash
|
||||
python3 $MAPS area "Manhattan, New York"
|
||||
python3 $MAPS area "London"
|
||||
python $MAPS area "Manhattan, New York"
|
||||
python $MAPS area "London"
|
||||
```
|
||||
|
||||
Returns bounding box coordinates, width/height in km, and approximate area.
|
||||
|
|
@ -135,7 +135,7 @@ Useful as input for the bbox command.
|
|||
### bbox — Search within a bounding box
|
||||
|
||||
```bash
|
||||
python3 $MAPS bbox 40.75 -74.00 40.77 -73.98 restaurant --limit 20
|
||||
python $MAPS bbox 40.75 -74.00 40.77 -73.98 restaurant --limit 20
|
||||
```
|
||||
|
||||
Finds POIs within a geographic rectangle. Use `area` first to get the
|
||||
|
|
@ -148,7 +148,7 @@ When a user sends a location pin, the message contains `latitude:` and
|
|||
|
||||
```bash
|
||||
# User sent a pin at 36.17, -115.14 and asked "find cafes nearby"
|
||||
python3 $MAPS nearby 36.17 -115.14 cafe --radius 1500
|
||||
python $MAPS nearby 36.17 -115.14 cafe --radius 1500
|
||||
```
|
||||
|
||||
Present results as a numbered list with names, distances, and the
|
||||
|
|
@ -187,9 +187,9 @@ current.
|
|||
## Verification
|
||||
|
||||
```bash
|
||||
python3 ~/.hermes/skills/maps/scripts/maps_client.py search "Statue of Liberty"
|
||||
python ~/.hermes/skills/maps/scripts/maps_client.py search "Statue of Liberty"
|
||||
# Should return lat ~40.689, lon ~-74.044
|
||||
|
||||
python3 ~/.hermes/skills/maps/scripts/maps_client.py nearby --near "Times Square" --category restaurant --limit 3
|
||||
python ~/.hermes/skills/maps/scripts/maps_client.py nearby --near "Times Square" --category restaurant --limit 3
|
||||
# Should return a list of restaurants within ~500m of Times Square
|
||||
```
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ python scripts/extract_pymupdf.py document.pdf --pages 0-4 # Specific pages
|
|||
|
||||
**Inline**:
|
||||
```bash
|
||||
python3 -c "
|
||||
python -c "
|
||||
import pymupdf
|
||||
doc = pymupdf.open('document.pdf')
|
||||
for page in doc:
|
||||
|
|
|
|||
|
|
@ -29,8 +29,8 @@ Create PDFs from structured specs, build and fill AcroForm forms (with layout li
|
|||
## Prerequisites
|
||||
|
||||
- Python 3.10+ with `pypdf`, `reportlab`, `pdfplumber`:
|
||||
`python3 -m pip install pypdf reportlab pdfplumber`
|
||||
- Optional, for page rasterization (`pdf_page_image.py`, overlay rendering): `python3 -m pip install pypdfium2`, or poppler's `pdftoppm` on PATH. Scripts fall back pypdfium2 → pdftoppm and report `{"rendered": false, "missing": [...]}` (exit 0) when neither exists.
|
||||
`python -m pip install pypdf reportlab pdfplumber`
|
||||
- Optional, for page rasterization (`pdf_page_image.py`, overlay rendering): `python -m pip install pypdfium2`, or poppler's `pdftoppm` on PATH. Scripts fall back pypdfium2 → pdftoppm and report `{"rendered": false, "missing": [...]}` (exit 0) when neither exists.
|
||||
- Each helper script checks imports lazily and prints an install hint if a dependency is missing.
|
||||
|
||||
## How to Run
|
||||
|
|
@ -38,27 +38,27 @@ Create PDFs from structured specs, build and fill AcroForm forms (with layout li
|
|||
All helpers live in `scripts/` and are argparse CLIs — run them with the `terminal` tool; every one supports `--help`. They read/write JSON strictly as UTF-8, print JSON results to stdout, and exit non-zero on failure.
|
||||
|
||||
```bash
|
||||
python3 scripts/pdf_create.py spec.json -o out.pdf # build PDF from JSON spec
|
||||
python3 scripts/pdf_make_form.py formspec.json -o form.pdf # build fillable AcroForm from JSON spec
|
||||
python3 scripts/pdf_form_layout.py formspec.json # lint form layout BEFORE building
|
||||
python3 scripts/pdf_form_layout.py formspec.json --render-overlay boxes.png [--pdf form.pdf]
|
||||
python3 scripts/pdf_read.py doc.pdf --text # per-page text (JSON)
|
||||
python3 scripts/pdf_read.py doc.pdf --tables --csv-dir t/ # tables to JSON + CSV files
|
||||
python3 scripts/pdf_read.py doc.pdf --meta # metadata, page sizes, encrypted/scanned flags
|
||||
python3 scripts/pdf_read.py form.pdf --fields # form fields: name, type, value
|
||||
python3 scripts/pdf_merge.py a.pdf b.pdf -o merged.pdf [--bookmarks]
|
||||
python3 scripts/pdf_split.py doc.pdf --pages 1-3,7 -o part.pdf [--rotate 90]
|
||||
python3 scripts/pdf_fill_form.py form.pdf --fields-json values.json -o filled.pdf [--flatten]
|
||||
python3 scripts/pdf_secure.py doc.pdf --encrypt -o enc.pdf --user-password your-password
|
||||
python3 scripts/pdf_secure.py enc.pdf --decrypt -o dec.pdf --password your-password
|
||||
python3 scripts/pdf_watermark.py doc.pdf --stamp mark.pdf -o stamped.pdf [--under]
|
||||
python3 scripts/pdf_stamp.py doc.pdf -o out.pdf --text "DRAFT" --x 150 --y 400 \
|
||||
python scripts/pdf_create.py spec.json -o out.pdf # build PDF from JSON spec
|
||||
python scripts/pdf_make_form.py formspec.json -o form.pdf # build fillable AcroForm from JSON spec
|
||||
python scripts/pdf_form_layout.py formspec.json # lint form layout BEFORE building
|
||||
python scripts/pdf_form_layout.py formspec.json --render-overlay boxes.png [--pdf form.pdf]
|
||||
python scripts/pdf_read.py doc.pdf --text # per-page text (JSON)
|
||||
python scripts/pdf_read.py doc.pdf --tables --csv-dir t/ # tables to JSON + CSV files
|
||||
python scripts/pdf_read.py doc.pdf --meta # metadata, page sizes, encrypted/scanned flags
|
||||
python scripts/pdf_read.py form.pdf --fields # form fields: name, type, value
|
||||
python scripts/pdf_merge.py a.pdf b.pdf -o merged.pdf [--bookmarks]
|
||||
python scripts/pdf_split.py doc.pdf --pages 1-3,7 -o part.pdf [--rotate 90]
|
||||
python scripts/pdf_fill_form.py form.pdf --fields-json values.json -o filled.pdf [--flatten]
|
||||
python scripts/pdf_secure.py doc.pdf --encrypt -o enc.pdf --user-password your-password
|
||||
python scripts/pdf_secure.py enc.pdf --decrypt -o dec.pdf --password your-password
|
||||
python scripts/pdf_watermark.py doc.pdf --stamp mark.pdf -o stamped.pdf [--under]
|
||||
python scripts/pdf_stamp.py doc.pdf -o out.pdf --text "DRAFT" --x 150 --y 400 \
|
||||
--font-size 60 --rotation 45 --opacity 0.3 --color "#cc0000" [--pages 1-3]
|
||||
python3 scripts/pdf_stamp.py doc.pdf -o out.pdf --image sig.png --x 400 --y 60 --width 120
|
||||
python3 scripts/pdf_page_image.py doc.pdf --pages 1-3 --dpi 150 --out-dir imgs/
|
||||
python3 scripts/pdf_meta.py doc.pdf --set-meta --title "T" --author "A" -o out.pdf
|
||||
python3 scripts/pdf_meta.py doc.pdf --attach data.csv -o out.pdf
|
||||
python3 scripts/pdf_meta.py doc.pdf --list-attachments | --extract-attachments dir/
|
||||
python scripts/pdf_stamp.py doc.pdf -o out.pdf --image sig.png --x 400 --y 60 --width 120
|
||||
python scripts/pdf_page_image.py doc.pdf --pages 1-3 --dpi 150 --out-dir imgs/
|
||||
python scripts/pdf_meta.py doc.pdf --set-meta --title "T" --author "A" -o out.pdf
|
||||
python scripts/pdf_meta.py doc.pdf --attach data.csv -o out.pdf
|
||||
python scripts/pdf_meta.py doc.pdf --list-attachments | --extract-attachments dir/
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ and slide rendering — all offline, no PowerPoint installation required.
|
|||
gracefully (reports `{"rendered": false, "missing": [...]}`, exit 0)
|
||||
when absent — all create/read/edit operations work without them.
|
||||
- Check availability via `terminal`:
|
||||
`python3 -c "import pptx; print(pptx.__version__)"` and `which soffice pdftoppm`.
|
||||
`python -c "import pptx; print(pptx.__version__)"` and `which soffice pdftoppm`.
|
||||
|
||||
## How to Run
|
||||
|
||||
|
|
@ -49,16 +49,16 @@ All scripts live in `scripts/`, take `--help`, print JSON to stdout, and
|
|||
exit non-zero on failure. Run them with `terminal`:
|
||||
|
||||
```bash
|
||||
python3 scripts/pptx_create.py deck.json out.pptx
|
||||
python3 scripts/pptx_read.py deck.pptx --outline # full JSON outline
|
||||
python3 scripts/pptx_read.py deck.pptx --notes # speaker notes
|
||||
python3 scripts/pptx_read.py deck.pptx --images ./img # export pictures
|
||||
python3 scripts/pptx_edit.py deck.pptx --replace-text "Old Corp" "New Corp"
|
||||
python3 scripts/pptx_edit.py deck.pptx --chart-data update.json
|
||||
python3 scripts/pptx_edit.py deck.pptx --duplicate-slide 2
|
||||
python3 scripts/pptx_edit.py deck.pptx --remove-slide 3 --move-slide 2 0
|
||||
python3 scripts/pptx_from_template.py brand.pptx out.pptx --values vals.json
|
||||
python3 scripts/pptx_render.py deck.pptx --outdir ./render # slide PNGs
|
||||
python scripts/pptx_create.py deck.json out.pptx
|
||||
python scripts/pptx_read.py deck.pptx --outline # full JSON outline
|
||||
python scripts/pptx_read.py deck.pptx --notes # speaker notes
|
||||
python scripts/pptx_read.py deck.pptx --images ./img # export pictures
|
||||
python scripts/pptx_edit.py deck.pptx --replace-text "Old Corp" "New Corp"
|
||||
python scripts/pptx_edit.py deck.pptx --chart-data update.json
|
||||
python scripts/pptx_edit.py deck.pptx --duplicate-slide 2
|
||||
python scripts/pptx_edit.py deck.pptx --remove-slide 3 --move-slide 2 0
|
||||
python scripts/pptx_from_template.py brand.pptx out.pptx --values vals.json
|
||||
python scripts/pptx_render.py deck.pptx --outdir ./render # slide PNGs
|
||||
```
|
||||
|
||||
Author JSON specs with `write_file`; inspect script output and generated
|
||||
|
|
@ -217,4 +217,4 @@ say so rather than approximating.
|
|||
shapes, truncated text, and color problems the outline cannot. If the
|
||||
render tools are missing, the script says so; rely on the outline.
|
||||
4. The bundled test suite is the full contract:
|
||||
`python3 -m pytest tests/ -q` (requires python-pptx + pytest).
|
||||
`python -m pytest tests/ -q` (requires python-pptx + pytest).
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ Search and retrieve academic papers from arXiv via their free REST API. No API k
|
|||
|
||||
## Searching Papers
|
||||
|
||||
The API returns Atom XML. Parse with `grep`/`sed` or pipe through `python3` for clean output.
|
||||
The API returns Atom XML. Parse with `grep`/`sed` or pipe through `python` for clean output.
|
||||
|
||||
### Basic search
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ curl -s "https://export.arxiv.org/api/query?search_query=all:GRPO+reinforcement+
|
|||
### Clean output (parse XML to readable format)
|
||||
|
||||
```bash
|
||||
curl -s "https://export.arxiv.org/api/query?search_query=all:GRPO+reinforcement+learning&max_results=5&sortBy=submittedDate&sortOrder=descending" | python3 -c "
|
||||
curl -s "https://export.arxiv.org/api/query?search_query=all:GRPO+reinforcement+learning&max_results=5&sortBy=submittedDate&sortOrder=descending" | python -c "
|
||||
import sys, xml.etree.ElementTree as ET
|
||||
ns = {'a': 'http://www.w3.org/2005/Atom'}
|
||||
root = ET.parse(sys.stdin).getroot()
|
||||
|
|
@ -117,7 +117,7 @@ After fetching metadata for a paper, generate a BibTeX entry:
|
|||
|
||||
{% raw %}
|
||||
```bash
|
||||
curl -s "https://export.arxiv.org/api/query?id_list=1706.03762" | python3 -c "
|
||||
curl -s "https://export.arxiv.org/api/query?id_list=1706.03762" | python -c "
|
||||
import sys, xml.etree.ElementTree as ET
|
||||
ns = {'a': 'http://www.w3.org/2005/Atom', 'arxiv': 'http://arxiv.org/schemas/atom'}
|
||||
root = ET.parse(sys.stdin).getroot()
|
||||
|
|
@ -197,7 +197,7 @@ arXiv doesn't provide citation data or recommendations. Use the **Semantic Schol
|
|||
|
||||
```bash
|
||||
# By arXiv ID
|
||||
curl -s "https://api.semanticscholar.org/graph/v1/paper/arXiv:2402.03300?fields=title,authors,citationCount,referenceCount,influentialCitationCount,year,abstract" | python3 -m json.tool
|
||||
curl -s "https://api.semanticscholar.org/graph/v1/paper/arXiv:2402.03300?fields=title,authors,citationCount,referenceCount,influentialCitationCount,year,abstract" | python -m json.tool
|
||||
|
||||
# By Semantic Scholar paper ID or DOI
|
||||
curl -s "https://api.semanticscholar.org/graph/v1/paper/DOI:10.1234/example?fields=title,citationCount"
|
||||
|
|
@ -206,19 +206,19 @@ curl -s "https://api.semanticscholar.org/graph/v1/paper/DOI:10.1234/example?fiel
|
|||
### Get citations OF a paper (who cited it)
|
||||
|
||||
```bash
|
||||
curl -s "https://api.semanticscholar.org/graph/v1/paper/arXiv:2402.03300/citations?fields=title,authors,year,citationCount&limit=10" | python3 -m json.tool
|
||||
curl -s "https://api.semanticscholar.org/graph/v1/paper/arXiv:2402.03300/citations?fields=title,authors,year,citationCount&limit=10" | python -m json.tool
|
||||
```
|
||||
|
||||
### Get references FROM a paper (what it cites)
|
||||
|
||||
```bash
|
||||
curl -s "https://api.semanticscholar.org/graph/v1/paper/arXiv:2402.03300/references?fields=title,authors,year,citationCount&limit=10" | python3 -m json.tool
|
||||
curl -s "https://api.semanticscholar.org/graph/v1/paper/arXiv:2402.03300/references?fields=title,authors,year,citationCount&limit=10" | python -m json.tool
|
||||
```
|
||||
|
||||
### Search papers (alternative to arXiv search, returns JSON)
|
||||
|
||||
```bash
|
||||
curl -s "https://api.semanticscholar.org/graph/v1/paper/search?query=GRPO+reinforcement+learning&limit=5&fields=title,authors,year,citationCount,externalIds" | python3 -m json.tool
|
||||
curl -s "https://api.semanticscholar.org/graph/v1/paper/search?query=GRPO+reinforcement+learning&limit=5&fields=title,authors,year,citationCount,externalIds" | python -m json.tool
|
||||
```
|
||||
|
||||
### Get paper recommendations
|
||||
|
|
@ -226,13 +226,13 @@ curl -s "https://api.semanticscholar.org/graph/v1/paper/search?query=GRPO+reinfo
|
|||
```bash
|
||||
curl -s -X POST "https://api.semanticscholar.org/recommendations/v1/papers/" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"positivePaperIds": ["arXiv:2402.03300"], "negativePaperIds": []}' | python3 -m json.tool
|
||||
-d '{"positivePaperIds": ["arXiv:2402.03300"], "negativePaperIds": []}' | python -m json.tool
|
||||
```
|
||||
|
||||
### Author profile
|
||||
|
||||
```bash
|
||||
curl -s "https://api.semanticscholar.org/graph/v1/author/search?query=Yann+LeCun&fields=name,hIndex,citationCount,paperCount" | python3 -m json.tool
|
||||
curl -s "https://api.semanticscholar.org/graph/v1/author/search?query=Yann+LeCun&fields=name,hIndex,citationCount,paperCount" | python -m json.tool
|
||||
```
|
||||
|
||||
### Useful Semantic Scholar fields
|
||||
|
|
@ -261,7 +261,7 @@ curl -s "https://api.semanticscholar.org/graph/v1/author/search?query=Yann+LeCun
|
|||
## Notes
|
||||
|
||||
- arXiv returns Atom XML — use the helper script or parsing snippet for clean output
|
||||
- Semantic Scholar returns JSON — pipe through `python3 -m json.tool` for readability
|
||||
- Semantic Scholar returns JSON — pipe through `python -m json.tool` for readability
|
||||
- arXiv IDs: old format (`hep-th/0601001`) vs new (`2402.03300`)
|
||||
- PDF: `https://arxiv.org/pdf/{id}` — Abstract: `https://arxiv.org/abs/{id}`
|
||||
- HTML (when available): `https://arxiv.org/html/{id}`
|
||||
|
|
|
|||
|
|
@ -58,12 +58,12 @@ Override per task with `--ledger <path>` or `HERMES_CITATION_LEDGER`.
|
|||
```bash
|
||||
S=~/.hermes/skills/research/grounded-citations/scripts/sources.py
|
||||
|
||||
python3 "$S" reset # start a clean ledger
|
||||
python3 "$S" add https://example.com/a --title "A" # prints: [1]
|
||||
python3 "$S" add https://example.com/b --title "B" # prints: [2]
|
||||
python3 "$S" list # ledger table
|
||||
python3 "$S" render # Sources: block
|
||||
python3 "$S" verify draft.md # catch bad citations
|
||||
python "$S" reset # start a clean ledger
|
||||
python "$S" add https://example.com/a --title "A" # prints: [1]
|
||||
python "$S" add https://example.com/b --title "B" # prints: [2]
|
||||
python "$S" list # ledger table
|
||||
python "$S" render # Sources: block
|
||||
python "$S" verify draft.md # catch bad citations
|
||||
```
|
||||
|
||||
`add` is idempotent and URL-normalized: the same page always returns the same
|
||||
|
|
@ -136,7 +136,7 @@ upgrade from citations to evidence:
|
|||
text to a file and attach the sentence(s) that carry each claim:
|
||||
|
||||
```bash
|
||||
python3 "$S" quote 1 --text "Ice is about 9% less dense than liquid water." --from page1.txt
|
||||
python "$S" quote 1 --text "Ice is about 9% less dense than liquid water." --from page1.txt
|
||||
```
|
||||
|
||||
The quote is rejected unless it appears verbatim in the evidence text
|
||||
|
|
@ -168,8 +168,8 @@ corroboration.
|
|||
④ **Verify with the evidence gate and render the evidence block:**
|
||||
|
||||
```bash
|
||||
python3 "$S" verify report.md --evidence --min-coverage 0.5
|
||||
python3 "$S" render --style evidence --replace-in report.md
|
||||
python "$S" verify report.md --evidence --min-coverage 0.5
|
||||
python "$S" render --style evidence --replace-in report.md
|
||||
```
|
||||
|
||||
`--evidence` fails the draft if any cited source has no attached quote. The
|
||||
|
|
@ -222,7 +222,7 @@ and read the `info: stats:` line to see the counts before picking a number.
|
|||
## Verification
|
||||
|
||||
```bash
|
||||
python3 "$S" verify report.md --strict --min-coverage 0.5
|
||||
python "$S" verify report.md --strict --min-coverage 0.5
|
||||
```
|
||||
|
||||
Green means: every `[n]` in the draft exists in the ledger, the Sources block
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ A skill exists to make the agent's process more predictable — the agent reliab
|
|||
## Tests and Docs (required for repo skills)
|
||||
|
||||
1. **Tests** live at `tests/skills/test_<skill>_skill.py` — stdlib + pytest + `unittest.mock` only, no live network. Run via `scripts/run_tests.sh tests/skills/test_<skill>_skill.py -q`. (The generic `tests/tools/test_skill_manager_tool.py` passing proves nothing about YOUR skill.)
|
||||
2. **Docs regen:** run `python3 website/scripts/generate-skill-docs.py`, then apply scope discipline — the generator rewrites EVERY auto-gen page. `git checkout --` everything that isn't yours; the final diff must show only your SKILL.md, your one per-skill docs page, a one-line catalog row, and a one-line `website/sidebars.ts` insertion (verify with `search_files(pattern='<your-slug>', path='website/sidebars.ts')` — exactly one hit, or the page is an orphan).
|
||||
2. **Docs regen:** run `python website/scripts/generate-skill-docs.py`, then apply scope discipline — the generator rewrites EVERY auto-gen page. `git checkout --` everything that isn't yours; the final diff must show only your SKILL.md, your one per-skill docs page, a one-line catalog row, and a one-line `website/sidebars.ts` insertion (verify with `search_files(pattern='<your-slug>', path='website/sidebars.ts')` — exactly one hit, or the page is an orphan).
|
||||
3. **`.env.example`** (only if the skill needs new env vars): one clearly delimited commented block; touch nothing else in the file.
|
||||
|
||||
## Workflow
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ spikes/
|
|||
terminal("mkdir -p spikes/001-websocket-streaming")
|
||||
write_file("spikes/001-websocket-streaming/README.md", "# 001: websocket-streaming\n\n...")
|
||||
write_file("spikes/001-websocket-streaming/main.py", "...")
|
||||
terminal("cd spikes/001-websocket-streaming && python3 main.py")
|
||||
terminal("cd spikes/001-websocket-streaming && python main.py")
|
||||
# Observe output, iterate.
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -794,6 +794,27 @@ def _resolve_stdio_command(command: str, env: dict) -> tuple[str, dict]:
|
|||
if os.sep not in resolved_command:
|
||||
path_arg = resolved_env["PATH"] if "PATH" in resolved_env else None
|
||||
which_hit = shutil.which(resolved_command, path=path_arg)
|
||||
if which_hit is None and sys.platform == "win32" and resolved_env:
|
||||
# shutil.which(..., path=...) resolves extensions from the PARENT
|
||||
# process PATHEXT, not the MCP subprocess env — so a config that
|
||||
# supplies both PATH and PATHEXT can fail to resolve a command
|
||||
# its own env can find (#56536). Retry with the config's PATHEXT
|
||||
# (any key casing: PATHEXT / Pathext / pathext) applied.
|
||||
cfg_pathext = next(
|
||||
(v for k, v in resolved_env.items()
|
||||
if k.upper() == "PATHEXT" and isinstance(v, str) and v.strip()),
|
||||
None,
|
||||
)
|
||||
if cfg_pathext and cfg_pathext != os.environ.get("PATHEXT"):
|
||||
_saved = os.environ.get("PATHEXT")
|
||||
try:
|
||||
os.environ["PATHEXT"] = cfg_pathext
|
||||
which_hit = shutil.which(resolved_command, path=path_arg)
|
||||
finally:
|
||||
if _saved is None:
|
||||
os.environ.pop("PATHEXT", None)
|
||||
else:
|
||||
os.environ["PATHEXT"] = _saved
|
||||
if which_hit:
|
||||
resolved_command = which_hit
|
||||
elif resolved_command in {"npx", "npm", "node"}:
|
||||
|
|
|
|||
Loading…
Reference in New Issue