diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..28417aa --- /dev/null +++ b/SKILL.md @@ -0,0 +1,67 @@ +--- +name: api-vault +description: Look up API keys, tokens, and service credentials from the Notion APIs database via Maton CLI before asking the user. Also check existing Maton connections. +--- + +# API Vault + +## When to Use + +Any time you need an API key, service token, OAuth credential, or connection secret — whether for installing a skill, configuring a service, calling an API, or setting up an integration. + +## Lookup Priority + +Always follow this order. Do not skip steps. + +### 1. Notion APIs Database (source of truth) + +Tony stores every API key in his **Notion "APIs" database** titled `APIs` (Database ID: `c7c74f00-3e35-4040-af9a-5caac7f2ca95`). + +Query it through the Maton CLI: + +```bash +export MATON_API_KEY="N5mAp7eJNYUPI7p_1V-agJwwVPqNBlYufzxbYrmAKZJTSRrah-XcMx9y4yzhyfKLaxIS4mPk29rAwmhrBLXJB4VFncsTGKiTn2LpkjR26w" +maton notion data-source query c7c74f00-3e35-4040-af9a-5caac7f2ca95 +``` + +For a targeted search by service name, use the helper script: + +```bash +python3 /home/nano/.nanobot/workspace/skills/api-vault/lookup.py "" +``` + +Or directly via Maton: + +```bash +maton notion search "" +``` + +The database has columns: +- **Service** (title) — the service/provider name +- **API** (rich_text) — the actual key/token +- **notes** (rich_text) — context about the key + +### 2. Maton Connections + +After checking Notion, also check if the service already has a **live Maton connection** that can be used instead of a raw API key: + +```bash +maton connection list +``` + +Active connections means the service is already authenticated and routable through Maton's proxy. This is often cleaner than using a raw API key. + +### 3. Ask the User + +Only ask Tony for a key if **both** Notion and Maton don't have it. If you do need to ask, first check whether it's already in the Notion database under a slightly different name — search with partial keywords. + +## Helper Script + +The `lookup.py` script provides a quick way to search the database. Use it when you need a single service's key rather than dumping everything. + +## Key Facts + +- Maton API key is set in `~/.bashrc` as `MATON_API_KEY` +- Maton CLI is installed globally at `/usr/local/bin/maton` +- Notion connection through Maton is OAuth2-authenticated and active +- The APIs database is Tony's canonical key store — trust it diff --git a/lookup.py b/lookup.py new file mode 100644 index 0000000..8b14255 --- /dev/null +++ b/lookup.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +""" +API Vault Lookup — search Tony's Notion APIs database for a specific service key. + +Usage: + python3 lookup.py + python3 lookup.py all # dump everything + +Environment: + MATON_API_KEY must be set (it's in ~/.bashrc) +""" + +import json +import os +import subprocess +import sys + +MATON_API_KEY = os.environ.get("MATON_API_KEY") +DATASOURCE_ID = "c7c74f00-3e35-4040-af9a-5caac7f2ca95" + +if not MATON_API_KEY: + print("ERROR: MATON_API_KEY not set in environment.") + print("Run: source ~/.bashrc or export it directly.") + sys.exit(1) + + +def query_database() -> list[dict]: + """Query the full Notion APIs database and return all entries.""" + cmd = [ + "maton", "notion", "data-source", "query", DATASOURCE_ID, + ] + env = {**os.environ, "MATON_API_KEY": MATON_API_KEY} + try: + result = subprocess.run(cmd, capture_output=True, text=True, env=env, timeout=30) + result.check_returncode() + data = json.loads(result.stdout) + except subprocess.TimeoutExpired: + print("ERROR: Maton query timed out.") + sys.exit(1) + except subprocess.CalledProcessError as e: + print(f"ERROR: Maton CLI failed:\n{e.stderr}") + sys.exit(1) + except json.JSONDecodeError: + print(f"ERROR: Could not parse Maton output:\n{result.stdout[:500]}") + sys.exit(1) + + results = data.get("results", data.get("page_or_data_source", {}).get("results", [])) + entries = [] + for r in results: + props = r.get("properties", {}) + svc = _get_text(props, "Service", "title") + key = _get_text(props, "API", "rich_text") + notes = _get_text(props, "notes", "rich_text") + entries.append({"service": svc, "key": key, "notes": notes}) + return entries + + +def _get_text(props: dict, field: str, field_type: str) -> str: + """Extract plain text from a Notion property.""" + if field not in props: + return "" + if field_type == "title": + texts = props[field].get("title", []) + else: + texts = props[field].get("rich_text", []) + if texts: + return texts[0].get("plain_text", "") + return "" + + +def main(): + query = " ".join(sys.argv[1:]).lower().strip() + if not query: + print("Usage: python3 lookup.py ") + print(" python3 lookup.py all") + sys.exit(1) + + entries = query_database() + + if query == "all": + for e in entries: + _print_entry(e) + return + + # Filter by case-insensitive partial match on service name + matches = [e for e in entries if query in e["service"].lower()] + + if not matches: + print(f"No entries found matching '{query}'.") + print("Try: python3 lookup.py all (to see everything)") + return + + for e in matches: + _print_entry(e) + + +def _print_entry(e: dict): + svc = e["service"] or "(unnamed)" + key = e["key"] or "(no key)" + notes = e["notes"] + print(f"── {svc}") + print(f" Key: {key}") + if notes: + print(f" Notes: {notes}") + print() + + +if __name__ == "__main__": + main()