Initial backup 2026-02-17

This commit is contained in:
Krilly
2026-02-17 15:50:53 +00:00
commit 8902a93add
941 changed files with 131420 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
---
name: notion-apikeys
description: Look up API keys from Anthony's Notion 'API' database. Use this skill whenever you need an API key, token, or secret for any service (Brave, OpenAI, Notion, etc.) and it isn't already configured. Instead of asking Anthony for the key manually, query his Notion database to retrieve it automatically. Triggers on missing API key errors, needing to configure a service, or any request to find/use a stored API key.
---
# Notion API Key Lookup
Query Anthony's Notion 'API' database to retrieve API keys and secrets.
## Config File
Credentials are stored at `~/.config/notion-apikeys.json`:
```json
{
"token": "secret_...",
"database_id": "..."
}
```
## Script
```
skills/notion-apikeys/scripts/query.py
```
## Usage
```bash
# Look up a specific service
python3 scripts/query.py "Brave"
# List all entries (keys are partially masked)
python3 scripts/query.py --list
# First-time setup (interactive)
python3 scripts/query.py --setup
# Discover what columns exist in the database
python3 scripts/query.py --find-db
# Pass token inline without saving
python3 scripts/query.py "Brave" --token secret_xxx
```
## Workflow: Retrieve a Key and Configure OpenClaw
1. Run `query.py <service>` with the service name
2. Extract the key from the output
3. Apply to OpenClaw config via `gateway config.patch` or edit `~/.openclaw/openclaw.json` directly
4. Restart gateway if needed
## Setup (First Time)
Anthony must:
1. Create a Notion integration at https://www.notion.so/my-integrations
2. Share the 'API' database page with that integration (Share → Invite)
3. Copy the integration token (starts with `secret_`)
4. Run: `python3 scripts/query.py --setup`
If the Notion token is already known, pass it directly:
```bash
python3 scripts/query.py "Brave" --token secret_xxxx
```
## Notion Database Structure
The script auto-discovers column names. Common layouts:
- **Name** (title) — service/provider name
- **API Key** / **Key** / **Token** — the actual secret
- **URL** / **Endpoint** — optional base URL
- **Notes** — optional context
If the property names differ, run `--find-db` to inspect them.
## Error Handling
- "Could not find 'API' database" → integration hasn't been shared with the database
- "Notion API error 401" → token is invalid or expired
- "No entries found for X" → check spelling; use `--list` to see all available entries

View File

@@ -0,0 +1,199 @@
#!/usr/bin/env python3
"""
notion-apikeys - Query Notion 'API' database for API keys
Usage:
query.py <service_name> Look up a service by name
query.py --list List all entries
query.py --find-db Discover database structure/properties
query.py --setup Interactive setup (save token + db-id)
"""
import sys
import json
import os
import argparse
CONFIG_FILE = os.path.expanduser("~/.config/notion-apikeys.json")
NOTION_VERSION = "2022-06-28"
NOTION_BASE = "https://api.notion.com/v1"
def get_headers(token):
return {
"Authorization": f"Bearer {token}",
"Notion-Version": NOTION_VERSION,
"Content-Type": "application/json",
}
def notion_request(method, path, token, payload=None):
try:
import urllib.request
import urllib.error
url = f"{NOTION_BASE}{path}"
data = json.dumps(payload).encode() if payload is not None else None
req = urllib.request.Request(url, data=data, headers=get_headers(token), method=method)
with urllib.request.urlopen(req) as resp:
return json.loads(resp.read())
except Exception as e:
if hasattr(e, "read"):
err = e.read().decode()
try:
err = json.loads(err)
except Exception:
pass
print(f"Notion API error: {err}", file=sys.stderr)
else:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
def find_database(token, name="API"):
result = notion_request("POST", "/search", token, {
"query": name,
"filter": {"value": "database", "property": "object"},
"page_size": 20,
})
dbs = result.get("results", [])
for db in dbs:
title_parts = db.get("title", [])
db_name = "".join(t.get("plain_text", "") for t in title_parts).strip()
if db_name.lower() == name.lower():
return db["id"], db_name, db.get("properties", {})
# If no exact match, return all found
return None, None, dbs
def query_database(token, db_id, filter_text=None):
payload = {"page_size": 100}
if filter_text:
payload["filter"] = {
"or": [
{"property": "Name", "title": {"contains": filter_text}},
]
}
return notion_request("POST", f"/databases/{db_id}/query", token, payload)
def extract_value(prop):
ptype = prop.get("type", "")
if ptype == "title":
return "".join(t.get("plain_text", "") for t in prop.get("title", []))
elif ptype == "rich_text":
return "".join(t.get("plain_text", "") for t in prop.get("rich_text", []))
elif ptype in ("url", "email", "phone_number"):
return prop.get(ptype) or ""
elif ptype == "select":
s = prop.get("select")
return s.get("name", "") if s else ""
elif ptype == "multi_select":
return ", ".join(s.get("name", "") for s in prop.get("multi_select", []))
elif ptype == "number":
v = prop.get("number")
return str(v) if v is not None else ""
elif ptype == "checkbox":
return "" if prop.get("checkbox") else ""
elif ptype == "date":
d = prop.get("date")
return d.get("start", "") if d else ""
return ""
def load_config():
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE) as f:
return json.load(f)
return {}
def save_config(config):
os.makedirs(os.path.dirname(CONFIG_FILE), exist_ok=True)
with open(CONFIG_FILE, "w") as f:
json.dump(config, f, indent=2)
def main():
parser = argparse.ArgumentParser(description="Notion API key lookup")
parser.add_argument("service", nargs="?", help="Service name to look up")
parser.add_argument("--list", action="store_true", help="List all entries")
parser.add_argument("--find-db", action="store_true", help="Discover DB structure")
parser.add_argument("--setup", action="store_true", help="Save token + db-id to config")
parser.add_argument("--token", help="Notion token (overrides config/env)")
parser.add_argument("--db-id", help="Notion database ID (overrides config)")
args = parser.parse_args()
config = load_config()
token = args.token or os.environ.get("NOTION_TOKEN") or config.get("token")
db_id = args.db_id or config.get("database_id")
# Setup mode
if args.setup:
if not token:
token = input("Notion Integration Token (secret_...): ").strip()
if not db_id:
print("Finding 'API' database automatically...")
found_id, found_name, _ = find_database(token, "API")
if found_id:
print(f"Found: '{found_name}' (ID: {found_id})")
db_id = found_id
else:
db_id = input("Database ID (or leave blank to auto-find each time): ").strip()
config["token"] = token
if db_id:
config["database_id"] = db_id
save_config(config)
print(f"✓ Config saved to {CONFIG_FILE}")
return
if not token:
print("ERROR: Notion token required.", file=sys.stderr)
print(" Run: query.py --setup", file=sys.stderr)
print(" Or set: NOTION_TOKEN env var", file=sys.stderr)
sys.exit(1)
# Auto-discover database if needed
if not db_id:
found_id, found_name, found_dbs = find_database(token, "API")
if found_id:
db_id = found_id
else:
print("ERROR: Could not find 'API' database.", file=sys.stderr)
print(f"Found databases: {[d.get('id') for d in found_dbs]}", file=sys.stderr)
sys.exit(1)
# Find database structure
if args.find_db:
found_id, found_name, props = find_database(token, "API")
if found_id:
print(f"Database: '{found_name}' (ID: {found_id})")
print("Properties:")
for name, prop in props.items():
print(f"{name} ({prop.get('type', '?')})")
return
# Query
filter_text = args.service if not args.list else None
result = query_database(token, db_id, filter_text)
pages = result.get("results", [])
if not pages:
label = f" for '{args.service}'" if args.service else ""
print(f"No entries found{label}.")
return
for page in pages:
props = page.get("properties", {})
values = {k: extract_value(v) for k, v in props.items()}
name = next((values[k] for k, v in props.items() if v.get("type") == "title"), "Unknown")
print(f"\n── {name} ──")
for k, val in values.items():
if val and props[k].get("type") != "title":
# Mask long keys for security in list mode
if args.list and len(val) > 20 and k.lower() in ("key", "api key", "token", "secret", "value"):
val = val[:8] + "..." + val[-4:]
print(f" {k}: {val}")
if __name__ == "__main__":
main()