diff --git a/README.md b/README.md index dd3179d..6be59c8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# gkeep +# Gkeep Google Keep notes via gkeepapi. List, search, create, and manage notes. diff --git a/SKILL.md b/SKILL.md index 0d02f01..04bc035 100644 --- a/SKILL.md +++ b/SKILL.md @@ -51,20 +51,9 @@ Delete (trash): gkeep delete ``` -Pin: -```bash -gkeep pin -``` - -Unpin: -```bash -gkeep unpin -``` - ## 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) diff --git a/gkeep.py b/gkeep.py deleted file mode 100644 index e5d8125..0000000 --- a/gkeep.py +++ /dev/null @@ -1,296 +0,0 @@ -#!/usr/bin/env python3 -""" -gkeep - CLI wrapper for gkeepapi -Usage: - gkeep login # Login and save token - gkeep list [--limit N] # List notes - gkeep search # Search notes - gkeep get # Get note by ID - gkeep create [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 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() diff --git a/package.json b/package.json index 4445235..6498abe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gkeep", - "version": "1.0.1", + "version": "1.0.0", "description": "Google Keep CLI wrapper for listing, creating, and managing notes via gkeepapi.", "bin": { "gkeep": "./gkeep" @@ -8,6 +8,6 @@ "scripts": { "postinstall": "python3 -m venv .venv && .venv/bin/pip install -r requirements.txt" }, - "author": "clawdbot", + "author": "VACInc", "license": "MIT" }