Skip to main content

CLI for Grug Notes API

Project description

grugnotes-cli

Command-line client for the Grug Notes /api/v1/ API.

The /api/v1/ API is public API surface. New server endpoints may be callable by custom clients before this CLI exposes first-class commands for them.

Setup

pip install grugnotes-cli
grugnotes auth          # opens settings page, paste your API key
grugnotes status        # verify connection

API keys are fixed to one space. A key may be broad within that space or limited to a selected prompt allowlist. Permissions are set at creation; create a new key if you need different access.

Security defaults

  • grugnotes auth stores the API key in plain text at ~/.grugnotes. The CLI sets that file to owner-only permissions (0600) and refuses symlinked config paths.
  • sync init --save-key and sync reset --save-key save the active API key into .grugnotes.json so each sync directory remembers its own key. This is useful when you have multiple API keys for different spaces. Keys from --api-key or GRUGNOTES_API_KEY are ephemeral by default and require --save-key to persist. Keys from ~/.grugnotes (global config) are always saved automatically. Precedence: --api-key > GRUGNOTES_API_KEY > .grugnotes.json (local) > ~/.grugnotes (global).
  • For CI, shared machines, or ephemeral shells, prefer GRUGNOTES_API_KEY instead of saving a key locally.
  • The CLI refuses plain http:// base URLs unless the host is loopback (localhost, 127.0.0.1, or ::1). To deliberately use insecure HTTP against a non-localhost host, pass --allow-insecure-http or set GRUGNOTES_ALLOW_INSECURE_HTTP=1.
  • Avoid --api-key when possible; shell history and process lists can leak it. Prefer grugnotes auth or GRUGNOTES_API_KEY.
  • Saved API keys are bound to the base URL they were stored with. If you switch --base-url or GRUGNOTES_BASE_URL, the CLI refuses to reuse the saved key unless you provide an explicit key for that host.
  • Sync commands refuse to operate on symlinked sync directories or directories that contain symlinked sync paths or prompt metadata directories.
  • sync init and sync reset write a local .gitignore covering .grugnotes/, .grugnotes.json, .prompt.json, and *.conflict.md.

Testing

On Apple Silicon, run CLI tests under the x86_64 virtualenv so the xxhash wheel matches the interpreter architecture:

source activate.sh && arch -x86_64 python -m pytest cli/grugnotes_cli/tests -q

Upgrade Note

Legacy .grugnotes.json entries that used sha256:... synced hashes are migrated in place to xxhash as files are scanned (sync status, sync push, sync pull, or sync watch startup). This avoids a one-time "push everything" after upgrade.

Legacy state entries with deleted_remotely=true are also migrated. During migration:

  • unchanged local tracked files are removed;
  • locally edited tracked files are moved to .grugnotes/trash/....

Commands

notes — list notes

grugnotes notes                              # all notes
grugnotes notes daily-notes                  # filter by prompt
grugnotes notes --from 2026-03-01 --to 2026-03-03
grugnotes notes --page 2 --limit 50           # pagination

read — read a note

grugnotes read 42                            # by id
grugnotes read 2026-03-03                    # list notes from a date
grugnotes read daily-notes                   # list notes from a prompt
grugnotes read daily-notes/2026-03-03        # by prompt + filename slug
grugnotes read daily-notes/2026-03-03-2      # suffixed same-date sibling

create — create a note

grugnotes create daily-notes "Hello world"
grugnotes create daily-notes "More text" --date 2026-03-03
grugnotes create daily-notes "Append this" --append

edit — edit a note

grugnotes edit 42 --notes "Full replacement"
grugnotes edit 42 --old "typo" --new "fixed"

prompts / prompt — list or show prompts

grugnotes prompts                             # list all
grugnotes prompts --page 2 --limit 50         # pagination
grugnotes prompt daily-notes                  # show one prompt

prompts-search — offline prompt search

Searches synced prompt metadata locally (no API call). Matches by exact name, then startswith, then contains.

grugnotes prompts-search daily

sync — initialize, inspect, and reconcile

grugnotes sync init
grugnotes sync init --prompt daily-notes
grugnotes sync init --from 2026-03-01 --to 2026-03-03
grugnotes sync init --save-key                # persist API key in this directory
grugnotes sync status
grugnotes sync pull
grugnotes sync push
grugnotes sync reset
grugnotes sync reset --save-key               # persist API key on reset
grugnotes sync watch                          # continuous sync
grugnotes sync watch --interval 60            # poll every 60s (default 30)

All sync subcommands except watch support --dry-run to preview changes without writing files. Sync commands exit with code 2 on unresolved conflicts. sync watch polls at the configured interval while there is recent sync activity, then slows remote checks to 60s after 5 minutes of quiet time. If the remote hash signal is temporarily unavailable, watch mode tolerates 3 unavailable poll cycles before falling back to a direct pull. Poll sleeps add up to 25% jitter to avoid synchronized bursts across many clients.

How sync works

The CLI tracks three versions of each note: the remote copy on the server, the local .md file, and a shadow (the last content both sides agreed on, stored in .grugnotes/shadows/).

On pull, the CLI fetches notes changed since the last sync. If only the remote changed, the local file is updated. If both sides changed the same note, a .conflict.md file is written and further syncs are blocked until you resolve it by deleting the conflict file.

On push, the CLI diffs local files against their shadows and sends a 3-way merge patch to the server. The server rejects the push if someone else saved in between (you'll need to pull first).

watch runs both continuously: a file watcher pushes local edits (debounced 3s), while a lightweight poll checks for remote changes every 30–60s.

File names mirror the server's canonical filename_slug. Untitled same-date siblings use suffixed files such as 2026-03-03-2.md; creating such a file and pushing it creates another untitled block on 2026-03-03.

After upgrading from older CLI versions, the first pull may rename files to match server canonical filename slugs. Renames are keyed by remote block ID; if a target path is occupied, sync writes a .conflict.md file instead of overwriting it.

Scripting pattern: pull → edit → push

When a script modifies synced files (e.g. appending changelog entries), always pull before editing to avoid overwriting changes made on the server:

grugnotes sync pull "$DIR"    # get latest from server
echo "new content" >> "$DIR/file.md"  # edit local files
grugnotes sync push "$DIR"   # push changes back

Pulls are incremental — only notes modified since the last sync are fetched.

JSON output

Most commands accept --json to output machine-readable data instead of human-formatted text. sync watch is the main exception because it is a long-running log stream. Useful for scripting:

grugnotes read daily-notes --json | jq '.data[].date'
grugnotes notes --json | jq '.data | length'

Global flags

grugnotes --base-url https://grugnotes.com status

Or use env vars:

export GRUGNOTES_API_KEY="gn_..."
export GRUGNOTES_BASE_URL="https://grugnotes.com"

AI agent integration

The package includes an AGENTS.md skill file with structured instructions for AI coding agents. After install, find it with:

python -c "import importlib.resources; print(importlib.resources.files('grugnotes_cli').parent / 'AGENTS.md')"

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

grugnotes_cli-0.1.1.tar.gz (38.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

grugnotes_cli-0.1.1-py3-none-any.whl (42.2 kB view details)

Uploaded Python 3

File details

Details for the file grugnotes_cli-0.1.1.tar.gz.

File metadata

  • Download URL: grugnotes_cli-0.1.1.tar.gz
  • Upload date:
  • Size: 38.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for grugnotes_cli-0.1.1.tar.gz
Algorithm Hash digest
SHA256 6e7dbb940770f75747dc2df7326c06ac8f2c930118dc6a71f67ade1ee21f482f
MD5 99258d59b5b264116d9216e6e2f700ce
BLAKE2b-256 7d6ca56ead22574d2a359cea76538090451a25c9748ee87ccea4c8cee6b6268e

See more details on using hashes here.

Provenance

The following attestation bundles were made for grugnotes_cli-0.1.1.tar.gz:

Publisher: publish.yml on keizo/grugnotes-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file grugnotes_cli-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: grugnotes_cli-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 42.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for grugnotes_cli-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 289e6cef0372dab5416961bdff9fc24b11805c57c5b0b60e1d41b3844c54ba15
MD5 dda01646be4e340d425bb3a7a134da6e
BLAKE2b-256 58f29ac9a67b3c15e6f07ee3387ebd4105bc2f735e5e9b6ac3bcb0fdb846097c

See more details on using hashes here.

Provenance

The following attestation bundles were made for grugnotes_cli-0.1.1-py3-none-any.whl:

Publisher: publish.yml on keizo/grugnotes-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page