@clawhub-promptingpufferfish-6909c57215
Google Keep notes via gkeepapi. List, search, create, manage notes. Add items to notes. Supports authorization via OAuth 2.0 Token.
---
name: gkeep-notes
description: Google Keep notes via gkeepapi. List, search, create, manage notes. Add items to notes. Supports authorization via OAuth 2.0 Token.
slash: gkeep-notes
version: 1.0.14
author: PromptingPufferfish
homepage: https://github.com/PromptingPufferfish/gkeep-notes
metadata:
openclaw:
emoji: "📝"
requires:
bins: ["python3"]
setup: |
cd ~/.openclaw/workspace/skills/gkeep-notes
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
---
# Google Keep Notes Skill 📝
## WHEN TO USE
Use this skill when users ask to:
- List Google Keep notes
- Search notes by keyword
- Create new notes
- Add items to notes
- Get specific note details
- Archive/pin/delete notes
**"List" = Google Keep note (not a bullet list)**
## SETUP (First Run Only)
1. Creates venv + installs requirements automatically
2. Manual call of generate_token.py from the shell, then copy & paste token to file `$HOME/.config/gkeep/token.json`.
```
gkeep.py list [--limit 10] # List notes
gkeep.py search "note_name" # Search notes
gkeep.py get <note_id> # Get note details
gkeep.py create "note_name" "note_body" # Create note
gkeep.py add <note_id> "new item" # Add item to note
gkeep.py archive <note_id> # Archive note
gkeep.py delete <note_id> # Trash note
gkeep.py pin <note_id> # Pin note
gkeep.py unpin <note_id> # Unpin note
```
## USAGE FLOW
```
1. User: "Show my <note_name>"
2. → gkeep.py list | grep <note_name> → note_id
3. → gkeep.py get <note_id> → show content
4. User: "Add milk to <note_name>"
5. → gkeep.py list | grep <note_name> → note_id
6. → gkeep.py add <note_id> "milk"
```
## EXECUTION TEMPLATE
```bash
cd ~/.openclaw/workspace/skills/gkeep-notes
source venv/bin/activate
python gkeep.py [command] [args]
```
## TROUBLESHOOTING
```
❌ "No token" → manually generate token with generate_token.py and copy token into token.json
❌ "Module not found" → setup properly
❌ "API changed" → Check GitHub issues
```
## NOTES
- Unofficial API (may break if Google changes)
- venv auto-created during setup
- Note IDs from `gkeep list` output
- Active project (updated March 2026)
```
FILE:README.md
# Info
gkeep-notes is an extension of openclaw/skills/vacinc/gkeep to make it work with authentication based on OAuth 2.0 Token. SKILL.md rewritten and "Add item" functionality added. New repository as openclaw/skills rejects pull requests. Released on clawhub.ai.
# 1. Install gkeep-notes skill
Run from the shell:
```bash
clawhub install gkeep-notes
```
```bash
sudo apt install python3-pip
sudo apt install python3.12-venv
python3 -m venv $HOME/.openclaw/workspace/skills/gkeep-notes/venv
source $HOME/.openclaw/workspace/skills/gkeep-notes/venv/bin/activate
pip install -r requirements.txt
```
# 2. Setup OAuth 2.0 Token Flow
When I published the skill on ClawHub.ai the check returned a warning because the user is asks to "handle sensitive session data in a nonstandard way" but OAuth 2.0 Token is the only authentication method which worked for me with a non-enterprise Google account.
## 2a. Manually create a token from the shell:
```bash
python3 -m venv $HOME/.openclaw/workspace/skills/gkeep-notes/venv
source $HOME/.openclaw/workspace/skills/gkeep-notes/venv/bin/activate
python $HOME/.openclaw/workspace/skills/gkeep-notes/generate_token.py
```
- Enter your email, choose option 2 OAuth Token
- Open [https://accounts.google.com/EmbeddedSetup](https://accounts.google.com/EmbeddedSetup) in your browser -> Login -> I agree -> F12 (Developer Tools) -> Application -> Cookies -> copy&paste value of oauth_token
- token is generated -> copy the token between "=== RESULT ===" and "✅ TOKEN"
## 2b. Create token.json:
```bash
mkdir $HOME/.config/gkeep/
nano $HOME/.config/gkeep/token.json
```
- paste token into the file
- save with Ctrl+O and Enter, exit with Ctrl+X
# 3. New skill init & consistency check
```bash
openclaw gateway restart
```
Double check if skill appears as "ready" in the list:
```bash
openclaw skills list --eligible
```
# 4. gkeep-notes skill usage
Via local OpenClaw chat:
```bash
/skill gkeep-notes add item "1 liter milk" to Google Keep note <note_name>
/skill gkeep-notes give me the content of Google Keep note <note_name>
```
FILE:generate_token.py
import gpsoauth
import json
print("=== GKEEP MASTER TOKEN GENERATOR ===")
email = input("Email: ")
print("\nOption 1: App-Passwort (2FA nötig)")
print("Option 2: OAuth Token (sicherer)")
choice = input("1 oder 2? ")
aid = "android-1234567890abcdef"
if choice == "1":
pw = input("App-Passwort (16-stellig): ")
resp = gpsoauth.perform_master_login(email, pw, aid)
else:
print("👉 https://accounts.google.com/EmbeddedSetup")
print("Login → I agree → F12 → Application → Cookies → oauth_token kopieren")
oauth = input("oauth_token: ")
resp = gpsoauth.exchange_token(email, oauth, aid)
print("\n=== RESULT ===")
print(json.dumps(resp, indent=2))
token = resp.get('Token')
if token:
print(f"\n✅ TOKEN: {token}")
print(f'\nIn config.json: "token": "{token}"')
else:
print("❌ FEHLER:", resp.get('Error'))
FILE:gkeep.py
#!/usr/bin/env python3
"""
gkeep - CLI wrapper for gkeepapi
Usage:
gkeep login <email> # Login and save token
gkeep list [--limit N] # List notes
gkeep search <query> # Search notes
gkeep get <note_id> # Get note by ID
gkeep create <title> [body] # Create a note
gkeep archive <note_id> # Archive a note
gkeep delete <note_id> # Delete (trash) a note
gkeep check <note_id> <text> # Check an item in a list note
gkeep add <note_id> <text> # Add item to list or append text
gkeep pin <note_id> # Pin a note
gkeep unpin <note_id> # Unpin a note
gkeep stats # Show note counts
"""
import sys
import os
import json
import getpass
from pathlib import Path
import gkeepapi
TOKEN_FILE = Path.home() / ".config" / "gkeep" / "token.json"
def load_keep():
keep = gkeepapi.Keep()
if TOKEN_FILE.exists():
data = json.loads(TOKEN_FILE.read_text())
keep.authenticate(data["Email"], data["Token"])
else:
print("Not logged in. Run: gkeep login <email>", file=sys.stderr)
sys.exit(1)
return keep
def cmd_login(email):
keep = gkeepapi.Keep()
password = os.environ.get("GKEEP_PASSWORD") or getpass.getpass(
"Password (or app password): "
)
try:
keep.login(email, password)
except Exception as e:
print(f"Login failed: {e}", file=sys.stderr)
print(
"Tip: Use an App Password from https://myaccount.google.com/apppasswords",
file=sys.stderr,
)
sys.exit(1)
TOKEN_FILE.parent.mkdir(parents=True, exist_ok=True)
TOKEN_FILE.write_text(
json.dumps({
"Email": email,
"Token": keep.getMasterToken(),
})
)
TOKEN_FILE.chmod(0o600)
print(f"Logged in as {email}. Token saved to {TOKEN_FILE}")
def cmd_list(limit=20):
keep = load_keep()
keep.sync()
notes = list(keep.all())[:int(limit)]
for note in notes:
if note.trashed or note.archived:
continue
title = note.title or "(untitled)"
preview = (note.text or "")[:50].replace("\n", " ")
print(f"[{note.id}] {title}: {preview}...")
def cmd_search(query):
keep = load_keep()
keep.sync()
results = keep.find(query=query)
for note in results:
if note.trashed:
continue
title = note.title or "(untitled)"
preview = (note.text or "")[:50].replace("\n", " ")
print(f"[{note.id}] {title}: {preview}...")
def cmd_get(note_id):
keep = load_keep()
keep.sync()
note = keep.get(note_id)
if not note:
print(f"Note not found: {note_id}", file=sys.stderr)
sys.exit(1)
print(f"Title: {note.title or '(untitled)'}")
print(f"ID: {note.id}")
print("---")
print(note.text or "")
def cmd_create(title, body=""):
keep = load_keep()
note = keep.createNote(title, body)
keep.sync()
print(f"Created note: {note.id}")
def cmd_archive(note_id):
keep = load_keep()
keep.sync()
note = keep.get(note_id)
if not note:
print(f"Note not found: {note_id}", file=sys.stderr)
sys.exit(1)
note.archived = True
keep.sync()
print(f"Archived: {note_id}")
def cmd_delete(note_id):
keep = load_keep()
keep.sync()
note = keep.get(note_id)
if not note:
print(f"Note not found: {note_id}", file=sys.stderr)
sys.exit(1)
note.trashed = True
keep.sync()
print(f"Deleted (trashed): {note_id}")
def cmd_check(note_id, query):
keep = load_keep()
keep.sync()
note = keep.get(note_id)
if not note:
print(f"Note not found: {note_id}", file=sys.stderr)
sys.exit(1)
found = False
if hasattr(note, "items"):
for item in note.items:
if query.lower() in item.text.lower() and not item.checked:
item.checked = True
found = True
print(f"Checked: {item.text}")
if found:
keep.sync()
print("Synced changes.")
elif hasattr(note, "items"):
print(f"No unchecked item found matching: {query}")
else:
print("This is not a list note.")
def cmd_add(note_id, text):
keep = load_keep()
keep.sync()
note = keep.get(note_id)
if not note:
print(f"Note not found: {note_id}", file=sys.stderr)
sys.exit(1)
if hasattr(note, "items"):
note.add(text, False)
keep.sync()
print(f"Added item: {text}")
else:
current = note.text or ""
note.text = current + "\n" + text
keep.sync()
print(f"Appended text: {text}")
def cmd_pin(note_id, pinned):
keep = load_keep()
keep.sync()
note = keep.get(note_id)
if not note:
print(f"Note not found: {note_id}", file=sys.stderr)
sys.exit(1)
note.pinned = pinned
keep.sync()
action = "Pinned" if pinned else "Unpinned"
print(f"{action}: {note_id}")
def cmd_stats():
keep = load_keep()
keep.sync()
active = 0
archived = 0
trashed = 0
pinned = 0
total = 0
for note in keep.all():
total += 1
if note.pinned and not note.trashed and not note.archived:
pinned += 1
if note.trashed:
trashed += 1
elif note.archived:
archived += 1
else:
active += 1
print("Your full breakdown:")
print(f"Active: {active}")
print(f"Pinned: {pinned}")
print(f"Archived: {archived}")
print(f"Trashed: {trashed}")
print(f"Total: {total}")
def main():
if len(sys.argv) < 2:
print(__doc__)
sys.exit(1)
cmd = sys.argv[1]
args = sys.argv[2:]
if cmd == "login":
if not TOKEN_FILE.exists():
if not args:
print("Usage: gkeep login <email>", file=sys.stderr)
sys.exit(1)
cmd_login(args[0])
else:
print("gkeep login is not needed, token file is used for authentication")
elif cmd == "list":
limit = 20
if "--limit" in args:
idx = args.index("--limit")
limit = args[idx + 1] if idx + 1 < len(args) else 20
cmd_list(limit)
elif cmd == "search":
if not args:
print("Usage: gkeep search <query>", file=sys.stderr)
sys.exit(1)
cmd_search(" ".join(args))
elif cmd == "get":
if not args:
print("Usage: gkeep get <note_id>", file=sys.stderr)
sys.exit(1)
cmd_get(args[0])
elif cmd == "create":
if not args:
print("Usage: gkeep create <title> [body]", file=sys.stderr)
sys.exit(1)
title = args[0]
body = " ".join(args[1:]) if len(args) > 1 else ""
cmd_create(title, body)
elif cmd == "archive":
if not args:
print("Usage: gkeep archive <note_id>", file=sys.stderr)
sys.exit(1)
cmd_archive(args[0])
elif cmd == "delete":
if not args:
print("Usage: gkeep delete <note_id>", file=sys.stderr)
sys.exit(1)
cmd_delete(args[0])
elif cmd == "stats":
cmd_stats()
elif cmd == "check":
if len(args) < 2:
print("Usage: gkeep check <note_id> <text>", file=sys.stderr)
sys.exit(1)
cmd_check(args[0], " ".join(args[1:]))
elif cmd == "add":
if len(args) < 2:
print("Usage: gkeep add <note_id> <text>", file=sys.stderr)
sys.exit(1)
cmd_add(args[0], " ".join(args[1:]))
elif cmd == "pin":
if not args:
print("Usage: gkeep pin <note_id>", file=sys.stderr)
sys.exit(1)
cmd_pin(args[0], True)
elif cmd == "unpin":
if not args:
print("Usage: gkeep unpin <note_id>", file=sys.stderr)
sys.exit(1)
cmd_pin(args[0], False)
else:
print(f"Unknown command: {cmd}", file=sys.stderr)
print(__doc__)
sys.exit(1)
if __name__ == "__main__":
main()
FILE:requirements.txt
gkeepapi>=0.14.0
google
google-auth-oauthlib
google-api-python-client