A slim, non-interactive command-line client for a personal Memos server, built to be driven by a coding agent. No TUI, no MCP server, no embedded LLM — just a scriptable CLI over a local SQLite cache (FTS5 full-text search) with read-time lazy sync, plus full coverage of the Memos v1 REST API.
Every read command supports --json; nothing ever prompts for input.
As of 2026-08-13, the latest stable Memos release is v0.30.0.
| Memos version | Status in memoq |
|---|---|
v0.30.0 |
Stable compatibility baseline; attachment and object-based relation requests covered by contract tests |
These are deterministic HTTP contract tests using local mock servers, not a
live Memos end-to-end test matrix. The generic memoq api command and raw
resource commands may work with other Memos v1 releases, but they are not part
of the stated compatibility baseline.
Requires Go 1.24+. Build a single self-contained binary (~15 MB, pure Go, no
CGO), then put it on your PATH:
make build
mv bin/memoq ~/bin/ # any directory on your PATHPoint memoq at your server and personal access token:
memoq config set server_url https://memos.example.com
memoq config set token <personal-access-token>
memoq config set auto_sync_ttl_seconds 30 # optional; 0 disables read-time auto-syncmemoq config list— dump the current config as JSON (the token is masked).memoq config get <key>— read one key (server_url/token/auto_sync_ttl_seconds).memoq config path— print the config file and DB file locations.
Data lives under $MEMOQ_HOME (default ~/.memoq). The token is stored 0600
and is never echoed back (config get token → ***set***).
memoq has two command tiers. Use the fast local-cache commands for everyday search/list/read/write; drop to the full-API resource commands for anything the cache doesn't model.
These serve a local SQLite cache and auto-sync from the server if it is staler
than auto_sync_ttl_seconds. Add --no-sync to skip the sync and use the cache
as-is; add --json to parse the output. Flags may appear before, after, or
between positional args.
# search (full-text, FTS5 with a LIKE substring fallback)
memoq search "deployment checklist" --json --limit 10
memoq search "同步" --json # CJK: use a short substring, not a full sentence
# list with filters (pinned-first, then newest-first)
memoq list --json --limit 50
memoq list --tag project --from 2026-01-01 --to 2026-06-30 --json
memoq list --visibility PUBLIC --offset 50 --json
# read one note
memoq get <uid> --json
# create (body from --content or stdin; --tag / --attach are repeatable; --visibility defaults to PRIVATE)
memoq create --content "Ship notes: cut RC on Friday" --tag release --tag ops
echo "multi-line body" | memoq create --tag inbox
memoq create --content "Design review" --attach ./diagram.png --attach ./notes.pdf
# update / delete
memoq update <uid> --content "revised body" --visibility PROTECTED
memoq delete <uid> --dry-run --json
memoq delete <uid> --yes --json
# overview + tag counts (handy to discover which tags exist before list --tag)
memoq stats --json
# force a full incremental sync from the server
memoq sync --jsonNotes:
- Freshness: reads auto-sync per the TTL. If you used
--no-sync, or agetmisses a just-created note, runmemoq syncand retry. - CJK search: FTS5 tokenizes Chinese poorly, so the LIKE fallback carries it —
search with a concrete substring rather than a full sentence. If a search
misses, fall back to
list --tag/ date filters. - No matches is normal, not an error — retry with different keywords.
- An
auto-sync skipped (...)line on stderr is non-fatal: the network/auth failed but the local cache is still served. Only a non-zero exit is a real failure. createandupdateare high-frequency operations and run directly. Add--dry-runonly when you want to preview them.- Destructive operations never prompt. Preview them with
--dry-run, obtain confirmation, then execute with--yes. create,update, anddeletesupport--jsonand return a consistent result containingoperation,uid, andcache_status.
Modeled on lark-cli's resource-group + verb structure. These hit the Memos v1
REST API directly and print the pretty-printed JSON response, giving complete
API coverage independent of the local cache.
| Group | Verbs |
|---|---|
memo |
list get create update delete comments comment relations relate set-relations reactions react unreact attachments set-attachments shares share unshare link-metadata |
attachment |
list get create update delete batch-delete pull |
user |
list get create update delete all-stats stats settings setting update-setting tokens create-token delete-token webhooks |
auth |
me signin signout refresh |
shortcut |
list get create update delete |
instance |
profile setting update-setting stats |
ai |
transcribe |
api |
<METHOD> <PATH> — generic escape hatch covering any endpoint |
Supply request bodies / query params with:
| Flag | Meaning |
|---|---|
--body '{...}' |
Raw JSON request body |
--body-file <path|-> |
JSON body from a file (- = stdin) |
--field k=v |
Body field, repeatable (value is JSON-parsed, string fallback) |
--query k=v |
Query parameter, repeatable |
--dry-run |
Print the planned request without sending it |
--yes |
Confirm a destructive operation |
--sync |
Sync the local memo cache after a successful request |
--json |
Accepted for consistency; resource commands always emit JSON |
attachment create additionally accepts --file <path>. memo set-attachments
accepts repeatable --attachment attachments/<id> values.
# typed resource verbs
memoq memo list --query pageSize=10 --query state=NORMAL
memoq memo create --field content='hi' --field visibility=PRIVATE
memoq memo update <uid> --field pinned=true --query updateMask=pinned
memoq memo delete <uid> --dry-run
memoq memo delete <uid> --yes
memoq memo relate <uid> <related-uid>
memoq memo attachments <uid> --json
memoq memo set-attachments <uid> --attachment attachments/<id>
memoq attachment create --file ./document.pdf
memoq user get me
memoq instance profile
# generic escape hatch — reaches any endpoint, current or future
memoq api GET /api/v1/memos --query pageSize=10
memoq api POST /api/v1/memos --field content='hello' --field visibility=PRIVATE
memoq api PATCH /api/v1/memos/<uid> --body '{"pinned":true}' --query updateMask=pinned --syncRun any group with no verb (e.g. memoq user) to print its verb list.
memo create,memo update, andmemo deletesync the local memo cache after success. Other resource commands leave it unchanged. Genericapicalls only sync when--syncis supplied. A cache-sync failure after a successful remote write is reported on stderr without converting the completed write into a command failure; runmemoq syncto repair the stale cache.
memo relateadds aREFERENCEwhile preserving existing relations. It uses the object-based relation format from the stable Memosv0.30.0API.memo set-relationsremains available for full replacement.
memoq remains fully non-interactive. create and update execute immediately
because they are frequent and recoverable. Destructive or high-impact commands
such as delete, batch-delete, share/unshare, token deletion, and user/instance
setting changes require --yes.
# Preview: no network request and no local mutation.
memoq delete <uid> --dry-run --json
# Execute only after the user approves the exact preview.
memoq delete <uid> --yes --jsonResource commands always produce JSON, including structured JSON errors.
Fast commands produce structured errors on stderr when --json is present.
Set MEMOQ_HTTP_DEBUG to inspect requests made through the req client. Debug
output goes to stderr, so JSON stdout stays parseable.
MEMOQ_HTTP_DEBUG=headers memoq memo list --query pageSize=10
MEMOQ_HTTP_DEBUG=trace memoq sync --json
MEMOQ_HTTP_DEBUG=dump memoq api GET /api/v1/auth/meSupported modes: headers, dump, trace, debug, dev. dump / dev
may expose tokens or memo content; attachment downloads do not dump response
body.
Attachment blob bytes are never returned by the JSON API — the Memos proto
marks the attachment content field INPUT_ONLY, so attachment list /
memo attachments <uid> only print metadata (name / filename / type /
size). The raw bytes are served by a separate file-server route,
/file/attachments/{id}/{filename}.
To let an agent actually read an image/file, use attachment pull:
memoq attachment pull <memo-uid> # download all of a memo's attachments
memoq attachment pull <memo-uid> --json # machine output with local_path per file
memoq attachment pull <memo-uid> --force # re-download even if a local copy existsIt downloads each blob to $MEMOQ_HOME/attachments/<id>/<filename>, records the
metadata + absolute local path in the SQLite cache, and prints where each file
landed (+ downloaded, = already present, - external link not downloaded).
With --json, parse the local_path field and Read that path directly. The
bearer token is attached to the download so PRIVATE/PROTECTED attachments work;
externally-linked attachments are recorded but not downloaded.
memoq create --attach <path> uploads a local file and associates it directly
with the newly created memo; repeat --attach for multiple files. Memos receives
attachment bytes as JSON base64, not multipart upload. Large files remain subject
to the instance upload limit (32 MiB by default when the server has no configured
limit). If an attachment upload fails after the memo is created, the command
reports the created memo UID and does not delete it.
To upload independently, use memoq attachment create --file <path>. Associate
the result with an existing memo using repeatable
memoq memo set-attachments <uid> --attachment attachments/<id>. This replaces
the memo's complete attachment set; clear it explicitly with
--body '{"attachments":[]}'.
main.go entry point → cli.Run
internal/config/ config + path resolution
internal/store/ SQLite store, FTS5, migrations, attachment cache
internal/memos/ Memos v1 API client (generic Do + typed helpers)
internal/syncer/ incremental sync engine
internal/cli/ command dispatch, output formatting
internal/cli/resource.go lark-cli-style resource-group + verb commands (full API)
internal/cli/attachments.go attachment blob download (attachment pull)
skills/memoq/SKILL.md companion Skill for coding agents
docs/ENDPOINTS.md canonical endpoint manifest (diff target)
docs/UPSTREAM_SYNC.md playbook for tracking upstream Memos API changes
go build ./... && go vet ./... && gofmt -l . && go test ./...HTTP requests use github.com/imroc/req/v3; SQLite uses the pure-Go
modernc.org/sqlite driver.