Skill: vacinc/gkeep (version 1.0.3)

--- name: gkeep description: Google Keep notes via gkeepapi. List, search, create, and manage notes. homepage: https://github.com/kiwiz/gkeepapi metadata: {"openclaw":{"emoji":"📝","requires":{"bins":["gkeep"]}}} --- # gkeep CLI wrapper for Google Keep using gkeepapi (unofficial API). ## Setup Login with your Google account: ```bash gkeep login [REDACTED_SECRET] ``` **Important:** Use an [App Password](https://myaccount.google.com/apppasswords), not your regular password. 2FA must be enabled. ## Commands List notes: ```bash gkeep list gkeep list --limit 10 ``` Search: ```bash gkeep search "shopping" ``` Get a specific note: ```bash gkeep get <note_id> ``` Create a note: ```bash gkeep create "Title" "Body text here" ``` Archive: ```bash gkeep archive <note_id> ``` Delete (trash): ```bash gkeep delete <note_id> ``` Pin: ```bash gkeep pin <note_id> ``` Unpin: ```bash gkeep unpin <note_id> ``` ## Notes - This uses an unofficial API that reverse-engineers Google Keep - Could break if Google changes their internal API - Token stored in `~/.config/gkeep/token.json` - First run bootstraps a local venv at `skills/gkeep/.venv` - Active project with recent updates (as of Jan 2026)

path: gkeep.py
content: #!/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()
    [REDACTED_SECRET]"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 args:
            print("Usage: gkeep login <email>", file=sys.stderr)
            sys.exit(1)
        cmd_login(args[0])
    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()


path: package.json
content: {
  "name": "gkeep",
  "version": "1.0.3",
  "description": "Google Keep CLI wrapper for listing, creating, and managing notes via gkeepapi.",
  "bin": {
    "gkeep": "./gkeep"
  },
  "scripts": {
    "postinstall": "python3 -m venv .venv && .venv/bin/pip install -r requirements.txt"
  },
  "author": "VACInc",
  "license": "MIT"
}


path: requirements.txt
content: gkeepapi>=0.14.0


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)