
Postgres
- 472 installs
- 364 repo stars
- Updated July 9, 2026
- sanjay3290/ai-skills
postgres is an agent skill (version 1.0) that runs read-only SELECT queries and schema exploration against configured PostgreSQL databases for developers who need safe production data inspection without write access.
About
postgres in sanjay3290/ai-skills (version 1.0, Apache 2.0) executes read-only SQL against one or more PostgreSQL databases via a Python query.py CLI and connections.json config. Agents list databases with --list, match user intent to connection descriptions, inspect tables and schemas, and run SELECT, SHOW, EXPLAIN, and WITH queries with a 30-second timeout and a 10,000-row cap. Safety layers include PostgreSQL readonly sessions, single-statement validation, SSL modes, credential sanitization in errors, and chmod 600 guidance for config files. Column output is capped at 100 characters per field for readable agent responses. Developers reach for postgres when exploring app data, validating migrations, or analyzing orders, users, or analytics tables during backend work. The skill blocks INSERT, UPDATE, DELETE, and DROP operations entirely. Requires Python 3.8+ and psycopg2-binary from requirements.txt with exit code 0 on success.
- postgres
Postgres by the numbers
- 472 all-time installs (skills.sh)
- +15 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #883 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/sanjay3290/ai-skills --skill postgresAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 472 |
|---|---|
| repo stars | ★ 364 |
| Last updated | July 9, 2026 |
| Repository | sanjay3290/ai-skills ↗ |
How do you query PostgreSQL safely in read-only mode?
Use postgres for development tasks
Who is it for?
Backend developers who need agent-assisted, read-only PostgreSQL exploration across staging or production replicas without opening a GUI client.
Skip if: Database migrations, schema writes, or teams that require INSERT/UPDATE/DELETE operations through the agent.
When should I use this skill?
User asks to query PostgreSQL, explore database schemas, run SELECT statements, or inspect table contents read-only.
What you get
Query result rows, table listings, schema metadata, and database inventory from configured PostgreSQL connections.
- SQL query results
- Table and schema listings
By the numbers
- Version 1.0 with 30-second query timeout and 10,000-row cap
- Requires Python 3.8+ and psycopg2-binary
Files
PostgreSQL Read-Only Query Skill
Execute safe, read-only queries against configured PostgreSQL databases.
Requirements
- Python 3.8+
- psycopg2-binary:
pip install -r requirements.txt
Setup
Create connections.json in the skill directory or ~/.config/claude/postgres-connections.json.
Security: Set file permissions to 600 since it contains credentials:
chmod 600 connections.json{
"databases": [
{
"name": "production",
"description": "Main app database - users, orders, transactions",
"host": "db.example.com",
"port": 5432,
"database": "app_prod",
"user": "readonly_user",
"password": "your-password",
"sslmode": "require"
}
]
}Config Fields
| Field | Required | Description |
|---|---|---|
| name | Yes | Identifier for the database (case-insensitive) |
| description | Yes | What data this database contains (used for auto-selection) |
| host | Yes | Database hostname |
| port | No | Port number (default: 5432) |
| database | Yes | Database name |
| user | Yes | Username |
| password | Yes | Password |
| sslmode | No | SSL mode: disable, allow, prefer (default), require, verify-ca, verify-full |
Usage
List configured databases
python3 scripts/query.py --listQuery a database
python3 scripts/query.py --db production --query "SELECT * FROM users LIMIT 10"List tables
python3 scripts/query.py --db production --tablesShow schema
python3 scripts/query.py --db production --schemaLimit results
python3 scripts/query.py --db production --query "SELECT * FROM orders" --limit 100Database Selection
Match user intent to database description:
| User asks about | Look for description containing |
|---|---|
| users, accounts | users, accounts, customers |
| orders, sales | orders, transactions, sales |
| analytics, metrics | analytics, metrics, reports |
| logs, events | logs, events, audit |
If unclear, run --list and ask user which database.
Safety Features
- Read-only session: Connection uses PostgreSQL
readonly=Truemode (primary protection) - Query validation: Only SELECT, SHOW, EXPLAIN, WITH queries allowed
- Single statement: Multiple statements per query rejected
- SSL support: Configurable SSL mode for encrypted connections
- Query timeout: 30-second statement timeout enforced
- Memory protection: Max 10,000 rows per query to prevent OOM
- Column width cap: 100 char max per column for readable output
- Credential sanitization: Error messages don't leak passwords
Troubleshooting
| Error | Solution |
|---|---|
| Config not found | Create connections.json in skill directory |
| Authentication failed | Check username/password in config |
| Connection timeout | Verify host/port, check firewall/VPN |
| SSL error | Try "sslmode": "disable" for local databases |
| Permission warning | Run chmod 600 connections.json |
Exit Codes
- 0: Success
- 1: Error (config missing, auth failed, invalid query, database error)
Workflow
1. Run --list to show available databases 2. Match user intent to database description 3. Run --tables or --schema to explore structure 4. Execute query with appropriate LIMIT
# Credentials - NEVER commit actual connection configs
connections.json
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
*.egg
*.egg-info/
dist/
build/
# Virtual environments
.venv/
venv/
ENV/
# Testing
.pytest_cache/
.coverage
htmlcov/
# IDE
.idea/
.vscode/
*.swp
*.swo
{
"databases": [
{
"name": "production",
"description": "Main production database - users, orders, transactions, accounts",
"host": "prod-db.example.com",
"port": 5432,
"database": "app_prod",
"user": "readonly_user",
"password": "your-password-here",
"sslmode": "require"
},
{
"name": "analytics",
"description": "Analytics warehouse - aggregated metrics, reports, historical data",
"host": "analytics-db.example.com",
"port": 5432,
"database": "analytics",
"user": "analyst",
"password": "your-password-here",
"sslmode": "require"
},
{
"name": "staging",
"description": "Staging environment - mirrors production for testing",
"host": "localhost",
"port": 5432,
"database": "app_staging",
"user": "dev",
"password": "dev-password",
"sslmode": "prefer"
}
]
}
postgres
Read-only PostgreSQL query skill. Query multiple databases safely with write protection.
Setup
1. Copy the example config:
cp connections.example.json connections.json2. Add your database credentials:
{
"databases": [
{
"name": "prod",
"description": "Production - users, orders, transactions",
"host": "db.example.com",
"port": 5432,
"database": "app_prod",
"user": "readonly",
"password": "secret",
"sslmode": "require"
}
]
}3. Secure the config:
chmod 600 connections.jsonUsage
# List configured databases
python3 scripts/query.py --list
# List tables
python3 scripts/query.py --db prod --tables
# Show schema
python3 scripts/query.py --db prod --schema
# Run query
python3 scripts/query.py --db prod --query "SELECT * FROM users" --limit 100Config Fields
| Field | Required | Default | Description |
|---|---|---|---|
| name | Yes | - | Database identifier |
| description | Yes | - | What data it contains (for auto-selection) |
| host | Yes | - | Hostname |
| port | No | 5432 | Port |
| database | Yes | - | Database name |
| user | Yes | - | Username |
| password | Yes | - | Password |
| sslmode | No | prefer | disable, allow, prefer, require, verify-ca, verify-full |
Safety Features
- Read-only sessions: PostgreSQL
readonly=Truemode blocks writes at database level - Query validation: Only SELECT, SHOW, EXPLAIN, WITH allowed
- Single statement: No multi-statement queries (prevents
SELECT 1; DROP TABLE) - Timeouts: 30s query timeout, 10s connection timeout
- Memory cap: Max 10,000 rows per query
- Credential protection: Passwords sanitized from error messages
Requirements
pip install psycopg2-binarypsycopg2-binary>=2.9,<3.0
#!/usr/bin/env python3
"""
Read-only PostgreSQL query executor.
Connects to configured databases and executes SELECT queries only.
"""
import json
import os
import re
import stat
import sys
import argparse
from pathlib import Path
from typing import Optional
try:
import psycopg2
except ImportError:
print("Error: psycopg2 not installed. Run: pip install psycopg2-binary")
sys.exit(1)
# Constants
SCRIPT_DIR = Path(__file__).parent.parent
CONFIG_LOCATIONS = [
SCRIPT_DIR / "connections.json",
Path.home() / ".config" / "claude" / "postgres-connections.json",
]
MAX_ROWS = 10000
MAX_COLUMN_WIDTH = 100
QUERY_TIMEOUT_MS = 30000
CONNECTION_TIMEOUT_SEC = 10
NULL_DISPLAY = "<NULL>"
def is_read_only(query: str) -> bool:
"""Basic client-side check. Primary protection is readonly=True session."""
query_upper = query.upper().strip()
safe_starts = ('SELECT', 'SHOW', 'DESCRIBE', 'EXPLAIN', 'WITH', '\\D')
return any(query_upper.startswith(cmd) for cmd in safe_starts)
def validate_single_statement(query: str) -> bool:
"""Check query contains only one statement."""
# Remove trailing semicolon and whitespace, then check for remaining semicolons
clean = query.rstrip().rstrip(';')
return ';' not in clean
def validate_config_permissions(path: Path) -> None:
"""Warn if config file has insecure permissions (Unix only)."""
if os.name != 'nt': # Skip on Windows
mode = path.stat().st_mode
if bool(mode & stat.S_IRWXG) or bool(mode & stat.S_IRWXO):
print(f"WARNING: {path} has insecure permissions!")
print(f"Config contains credentials. Run: chmod 600 {path}")
def validate_db_config(db: dict) -> None:
"""Validate required fields exist in database config."""
required = ['name', 'host', 'database', 'user', 'password']
missing = [f for f in required if f not in db]
if missing:
print(f"Error: Database config missing fields: {', '.join(missing)}")
sys.exit(1)
def find_config() -> Optional[Path]:
"""Find config file in supported locations."""
for path in CONFIG_LOCATIONS:
if path.exists():
return path
return None
def load_config(config_path: Optional[Path] = None) -> dict:
"""Load database connections from JSON config."""
path = config_path or find_config()
if not path:
print("Config not found. Searched:")
for loc in CONFIG_LOCATIONS:
print(f" - {loc}")
print("\nCreate connections.json with format:")
print(json.dumps({
"databases": [{
"name": "mydb",
"description": "Description of database contents",
"host": "localhost",
"port": 5432,
"database": "mydb",
"user": "user",
"password": "password",
"sslmode": "prefer"
}]
}, indent=2))
sys.exit(1)
validate_config_permissions(path)
with open(path) as f:
return json.load(f)
def list_databases(config: dict) -> None:
"""List all configured databases."""
print("Configured databases:\n")
for db in config.get("databases", []):
validate_db_config(db)
print(f" [{db['name']}]")
print(f" Host: {db['host']}:{db.get('port', 5432)}")
print(f" Database: {db['database']}")
print(f" Description: {db.get('description', 'No description')}")
print()
def execute_query(db_config: dict, query: str, limit: Optional[int] = None) -> None:
"""Execute a read-only query against the specified database."""
if not is_read_only(query):
print("Error: Only read-only queries (SELECT, SHOW, EXPLAIN) are allowed.")
sys.exit(1)
if not validate_single_statement(query):
print("Error: Multiple statements not allowed. Execute queries separately.")
sys.exit(1)
# Apply limit using regex to avoid false positives from string content
if limit and not re.search(r'\bLIMIT\s+\d+', query, re.IGNORECASE):
query = f"{query.rstrip(';')} LIMIT {limit}"
conn = None
try:
conn = psycopg2.connect(
host=db_config['host'],
port=db_config.get('port', 5432),
database=db_config['database'],
user=db_config['user'],
password=db_config['password'],
sslmode=db_config.get('sslmode', 'prefer'),
connect_timeout=CONNECTION_TIMEOUT_SEC,
options=f'-c statement_timeout={QUERY_TIMEOUT_MS}'
)
# Primary safety: readonly session prevents any write operations
conn.set_session(readonly=True, autocommit=True)
with conn.cursor() as cur:
cur.execute(query)
if cur.description:
columns = [desc[0] for desc in cur.description]
rows = cur.fetchmany(MAX_ROWS)
truncated = len(rows) == MAX_ROWS
# Calculate column widths with cap
widths = [min(len(col), MAX_COLUMN_WIDTH) for col in columns]
for row in rows:
for i, val in enumerate(row):
val_str = str(val) if val is not None else NULL_DISPLAY
widths[i] = min(max(widths[i], len(val_str)), MAX_COLUMN_WIDTH)
# Print header
header = " | ".join(col[:MAX_COLUMN_WIDTH].ljust(widths[i]) for i, col in enumerate(columns))
print(header)
print("-" * len(header))
# Print rows
for row in rows:
cells = []
for i, val in enumerate(row):
val_str = str(val) if val is not None else NULL_DISPLAY
if len(val_str) > MAX_COLUMN_WIDTH:
val_str = val_str[:MAX_COLUMN_WIDTH-3] + "..."
cells.append(val_str.ljust(widths[i]))
print(" | ".join(cells))
msg = f"\n({len(rows)} rows)"
if truncated:
msg += f" [truncated at {MAX_ROWS}]"
print(msg)
else:
print("Query executed (no result set returned)")
except psycopg2.Error as e:
error_msg = str(e)
# Sanitize to avoid leaking credentials
if 'password' in error_msg.lower() or 'authentication' in error_msg.lower():
error_msg = "Authentication failed. Check credentials in connections.json"
print(f"Database error: {error_msg}")
sys.exit(1)
finally:
if conn:
conn.close()
def find_database(config: dict, name: str) -> dict:
"""Find database config by name (case-insensitive)."""
for db in config.get("databases", []):
if db.get('name', '').lower() == name.lower():
validate_db_config(db)
return db
available = [db.get('name', 'unnamed') for db in config.get("databases", [])]
print(f"Database '{name}' not found.")
print(f"Available: {', '.join(available)}")
sys.exit(1)
def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Execute read-only PostgreSQL queries",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s --list
%(prog)s --db mydb --tables
%(prog)s --db mydb --query "SELECT * FROM users" --limit 100
"""
)
parser.add_argument("--config", "-c", type=Path, help="Path to config JSON")
parser.add_argument("--db", "-d", help="Database name to query")
parser.add_argument("--query", "-q", help="SQL query to execute")
parser.add_argument("--limit", "-l", type=int, help="Limit rows returned")
parser.add_argument("--list", action="store_true", help="List configured databases")
parser.add_argument("--schema", "-s", action="store_true", help="Show database schema")
parser.add_argument("--tables", "-t", action="store_true", help="List tables")
args = parser.parse_args()
config = load_config(args.config)
if args.list:
list_databases(config)
return
if not args.db:
print("Error: --db required. Use --list to see available databases.")
sys.exit(1)
db_config = find_database(config, args.db)
if args.tables:
query = """
SELECT table_schema, table_name, table_type
FROM information_schema.tables
WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
ORDER BY table_schema, table_name
"""
execute_query(db_config, query, args.limit)
elif args.schema:
query = """
SELECT c.table_schema, c.table_name, c.column_name, c.data_type, c.is_nullable
FROM information_schema.columns c
JOIN information_schema.tables t ON c.table_name = t.table_name AND c.table_schema = t.table_schema
WHERE c.table_schema NOT IN ('pg_catalog', 'information_schema')
ORDER BY c.table_schema, c.table_name, c.ordinal_position
"""
execute_query(db_config, query, args.limit)
elif args.query:
execute_query(db_config, args.query, args.limit)
else:
print("Error: --query, --tables, or --schema required")
sys.exit(1)
if __name__ == "__main__":
main()
Related skills
Forks & variants (1)
Postgres has 1 known copy in the catalog totaling 23 installs. They canonicalize to this original listing.
- sanjay3290 - 23 installs
How it compares
Pick postgres for safe read-only SQL exploration via agents; pick migration or ORM skills when schema changes or writes are required.
FAQ
Is the postgres skill write-safe?
postgres enforces read-only PostgreSQL sessions, validates that only SELECT, SHOW, EXPLAIN, and WITH queries run, rejects multiple statements, and blocks INSERT, UPDATE, DELETE, and DROP operations.
How does postgres choose a database?
postgres reads connections.json descriptions and matches user intent—users, orders, analytics, or logs—to the right database, or lists all configured databases when ambiguous.