An open API service indexing awesome lists of open source software.

https://github.com/olgasafonova/miro-cli

Single-binary CLI that wraps the Miro REST API as shell commands, with a local SQLite mirror for offline search.
https://github.com/olgasafonova/miro-cli

claude claude-code cli cobra collaboration go golang miro miro-api whiteboard

Last synced: about 7 hours ago
JSON representation

Single-binary CLI that wraps the Miro REST API as shell commands, with a local SQLite mirror for offline search.

Awesome Lists containing this project

README

          

# Miro Developer Platform CLI

Drive Miro from your shell. Bulk-migrate stickies between boards, audit
board access, or script Miro operations in CI. One verb per Miro REST
endpoint, JSON in and out, with a local SQLite mirror for offline search.

## Why use it

- **Shell-first.** Every endpoint is a flag-driven command. No hand-rolled
JSON envelopes, no pagination boilerplate, no curl + jq pipelines.
- **Agent-friendly.** `--json`, `--dry-run`, `--select`, and `--agent` are
built in so the output is predictable for scripts and LLMs.
- **Offline workflows.** `miro-cli sync` mirrors boards and items into a
local SQLite store; `miro-cli query` runs SQL and FTS5 search against it
without spending API quota.
- **Bulk verbs.** `items bulk-create`, `items bulk-update`,
`items bulk-delete`, and `stickies create-grid` collapse N HTTP calls
into one for workshop and migration flows.
- **Safe defaults.** Destructive verbs refuse to run without `--yes` (or
`--agent`, which implies it). `--idempotent` makes create retries safe.

## When to reach for what

| You want to... | Use |
| --- | --- |
| Script Miro from bash, CI, or a Makefile | `miro-cli` (this repo) |
| Drive Miro from Claude Code as a skill | `miro-cli` + the bundled `SKILL.md` |
| Drive Miro from a Claude Desktop / MCP-compatible agent | [miro-mcp-server](https://github.com/olgasafonova/miro-mcp-server) |
| Render Miro data as interactive UI in chat (cards, tables, SVG graphs) | [miro-mcp-apps](https://github.com/olgasafonova/miro-mcp-apps) |
| Embed Miro into a TypeScript or Python app | The official Miro SDKs |
| Issue a one-off API call to test a payload | `miro-cli --dry-run`, or `curl` |

The CLI, the MCP server, and the MCP Apps server are complements, not
alternatives: same author, overlapping API coverage, different runtimes.
Use whichever ones fit. They all read the same `MIRO_ACCESS_TOKEN`.

## Install

### From source

```bash
go install github.com/olgasafonova/miro-cli/cmd/miro-cli@latest
```

This drops a `miro-cli` binary in `$GOPATH/bin` (typically `~/go/bin`). Make
sure that directory is on your `PATH`.

### Pre-built binary

Download the appropriate archive for your platform from the
[latest release](https://github.com/olgasafonova/miro-cli/releases/latest)
and extract the `miro-cli` binary into a directory on your `PATH`. On macOS,
clear the Gatekeeper quarantine: `xattr -d com.apple.quarantine miro-cli`.
On Unix, mark it executable: `chmod +x miro-cli`.

### Homebrew

```bash
brew install olgasafonova/tap/miro-cli
```

## Quick Start

### 1. Set your access token

Get an access token from the
[Miro developer portal](https://miro.com/app/settings/user-profile/apps) and
export it:

```bash
export MIRO_ACCESS_TOKEN="your-token-here"
```

Or pass `--token` on every invocation.

### 2. Try your first command

```bash
miro-cli boards list
miro-cli boards get --board-id
miro-cli stickies create --board-id --content "Hello, Miro" --color yellow
```

Run `miro-cli --help` for the full command reference, or
`miro-cli --help` for the verbs under any group.

## Commands

Twenty-two resource groups today. The tables below summarize each group at
a glance; run `miro-cli --help` for the full verb list and flags.

### Board management

| Group | Purpose | Key verbs |
| --- | --- | --- |
| `boards` | Board lifecycle plus a few search / content helpers | `list`, `get`, `create`, `update`, `delete`, `copy`, `find`, `search`, `content`, `summary`, `picture`, `share`, `diagram`, `audit` |
| `members` | Board access control | `list`, `get`, `update`, `remove` |

### Item CRUD (one group per item type)

Each group below ships `create` / `get` / `update` / `delete` against its
own Miro REST resource. The table only calls out non-standard verbs.

| Group | Resource path | Extras |
| --- | --- | --- |
| `stickies` | `/v2/boards/{id}/sticky_notes` | `create-grid` (bulk row-major layout) |
| `shapes` | `/v2/boards/{id}/shapes` | `create-flowchart` (v2-experimental stencils) |
| `texts` | `/v2/boards/{id}/texts` | — |
| `frames` | `/v2/boards/{id}/frames` | — |
| `cards` | `/v2/boards/{id}/cards` | — |
| `app-cards` | `/v2/boards/{id}/app_cards` | — |
| `connectors` | `/v2/boards/{id}/connectors` | `list` |
| `embeds` | `/v2/boards/{id}/embeds` | — |
| `documents` | `/v2/boards/{id}/documents` and `/v2/boards/{id}/docs` | `upload` (multipart from disk), `update-from-file` (replace bytes), `create-doc` (Markdown rich-text) |
| `images` | `/v2/boards/{id}/images` | `upload`, `update-from-file` |
| `codewidgets` | `/v2-experimental/boards/{id}/code_widgets` | read-only `list` (experimental) |
| `mindmap` | `/v2-experimental/boards/{id}/mindmap_nodes` | `list` |
| `tables` | `/v2/boards/{id}/data_table_formats` | read-only `list` and `get` |

### Cross-type item operations

`items` is the catch-all for verbs that don't care about the underlying
type. Bulk verbs are here because Miro's bulk endpoint accepts a typed
array, not per-resource arrays.

| Verb | What it does |
| --- | --- |
| `items list` / `items list-all` | Cursor-paginated list, or fetch everything until the cursor exhausts |
| `items get` | Get any item by ID (returns the right shape based on `type`) |
| `items update` / `items delete` | Cross-type partial update + delete |
| `items bulk-create` | Create up to 20 items per call, mixed types |
| `items bulk-update` | PATCH many items in one logical call (serial under the hood) |
| `items bulk-delete` | DELETE many items in one logical call |
| `items get-within-frame` | List items inside a frame |
| `items get-by-tag` | List items carrying a tag |
| `items get-tags` / `attach-tag` / `detach-tag` | Tag membership for a single item |

### Tags and grouping

| Group | Purpose | Verbs |
| --- | --- | --- |
| `tags` | Tag definitions on a board | `list`, `get`, `create`, `update`, `delete` |
| `groups` | Group items so they move/resize as one | `list`, `get`, `get-items`, `create`, `update` (full PUT), `delete` (ungroup, items survive) |

### Local store (offline workflow)

| Command | What it does |
| --- | --- |
| `sync` | Mirror boards + items into a local SQLite store (incremental by default) |
| `query` | Run a read-only SQL or FTS5 query against the store |

See the [Local Store](#local-store) section below for the full workflow.

### Enterprise / audit

| Group | Purpose | Notes |
| --- | --- | --- |
| `audit` | Enterprise audit log (`list-logs`) | Last 90 days; CSV export for older data |
| `exports` | Org-scoped board export jobs (eDiscovery) | `create-job`, `list-job-tasks`, `get-job-status`, `get-job-results`, `get-task-link`, `update-job` |

Both require Enterprise-plan tokens with the matching scopes.

## Output Formats

```bash
# Human-readable table (default in terminal, JSON when piped)
miro-cli boards get

# JSON for scripting and agents
miro-cli boards get --json

# Filter to specific fields
miro-cli boards get --json --select id,name,status

# Dry run, show the request without sending
miro-cli boards get --dry-run

# Agent mode, JSON + compact + no prompts in one flag
miro-cli boards get --agent
```

## Local Store

`miro-cli` ships a local SQLite mirror of your boards and items so you can
run ad-hoc SQL and full-text search without burning API calls or waiting
for network round-trips. The default store path is
`$XDG_DATA_HOME/miro-cli/store.db` (or `~/.local/share/miro-cli/store.db`);
override it for any command with `--store-path`.

### `miro-cli sync`, populate the store

```bash
# Incremental: re-fetches a board's items only when modifiedAt has advanced
# past the stored watermark. First run does a full sweep.
miro-cli sync

# Force a full re-fetch (after a schema change, or when you suspect drift)
miro-cli sync --full

# Pin the watermark for one run, useful when a previous sync was interrupted
miro-cli sync --since 2026-05-14T00:00:00Z
```

Conflict policy is last-write-wins: the API is the source of truth, and
rows are upserted by id. The watermark is stamped at the start of a
successful run, so a mid-run failure leaves the previous watermark in
place and the next run resumes.

### `miro-cli query`, SQL passthrough

```bash
# Read-only SELECT against the store. Output is JSON when piped,
# tab-separated table when stdout is a terminal.
miro-cli query "SELECT id, name FROM boards ORDER BY modified_at DESC LIMIT 10"

# Items on a board, by type
miro-cli query "SELECT type, COUNT(*) FROM items WHERE board_id = 'b1' GROUP BY type"
```

The connection is opened with `mode=ro` and `PRAGMA query_only=ON`;
non-SELECT input is rejected before execution. `--limit` (default `1000`)
caps the rows returned per query, pass `--limit 0` to disable the cap.

### Full-text search

An `items_fts` FTS5 virtual table is kept in lockstep with `items` via
triggers. It indexes the textual fields Miro models as `data.content`,
`data.title`, and `data.description`: sticky notes, cards, text widgets,
shapes, app cards, and frame titles.

```bash
# Find every item mentioning "roadmap" across all synced boards
miro-cli query "SELECT item_id, board_id, item_type FROM items_fts WHERE items_fts MATCH 'roadmap'"

# Phrase match, adjacent tokens in any indexed field
miro-cli query 'SELECT item_id FROM items_fts WHERE items_fts MATCH "Q3 review"'

# Join back to items for richer columns
miro-cli query "SELECT i.id, i.type, i.position_x, i.position_y
FROM items_fts f JOIN items i ON i.id = f.item_id
WHERE items_fts MATCH 'design'"
```

The tokenizer is `unicode61` (FTS5's default) and does not stem, so `fox`
will not match `foxes`. Use the `*` suffix for prefix matching: `MATCH 'fox*'`.

## Agent Usage

This CLI is designed for AI agent consumption:

- **Non-interactive.** Never prompts; every input is a flag.
- **Pipeable.** `--json` writes structured output to stdout; errors go to stderr.
- **Filterable.** `--select id,name,status` returns only the fields you need.
- **Previewable.** `--dry-run` prints the request without sending it.
- **Idempotent create.** `--idempotent` makes create-then-retry safe.
- **Confirmable.** `--yes` (or `--agent`, which implies it) is required for destructive verbs.
- **Offline-friendly.** `miro-cli sync` mirrors boards + items locally; `miro-cli query` runs SQL and FTS5 search against the mirror without API round-trips.
- **Agent-safe by default.** No ANSI colors or formatting in output.

Exit codes: `0` success, `2` usage error, `3` not found, `4` auth error,
`5` API error, `7` rate limited, `10` config error.

## Use with Claude Code

This repo ships a `SKILL.md` that drives the CLI from Claude Code. Install
the binary first (see [Install](#install) above), then either drop
`SKILL.md` into your project's `.claude/skills/miro-cli/` directory or load
it via your skills-installer of choice. The skill verifies the binary is on
`$PATH` before running any command.

For Miro MCP tools (board operations, stickies, frames, diagrams), use the
separate [miro-mcp-server](https://github.com/olgasafonova/miro-mcp-server)
project instead. This CLI is shell + skill only.

## Configuration

`miro-cli` reads credentials from two sources, in precedence order:

1. `--token ` on any command
2. `MIRO_ACCESS_TOKEN` environment variable

A config file is not currently supported.

## Troubleshooting

**Authentication errors (exit code 4)**

- Verify the environment variable is set: `echo $MIRO_ACCESS_TOKEN`
- Try passing the token explicitly on one command: `miro-cli --token boards list`
- Generate a fresh token at https://miro.com/app/settings/user-profile/apps

**Not found errors (exit code 3)**

- Check the resource ID is correct
- Run the `list` verb under the relevant group to see available items

## Further reading

Miro Developer Platform background, in case this is your first time touching
the REST API:

- [Platform introduction](https://beta.developers.miro.com/docs/introduction)
- [REST API quickstart (video)](https://beta.developers.miro.com/docs/try-out-the-rest-api-in-less-than-3-minutes)
- [REST API quickstart (article)](https://beta.developers.miro.com/docs/build-your-first-hello-world-app-1)
- [Getting started with OAuth 2.0](https://beta.developers.miro.com/docs/getting-started-with-oauth)
- [Miro App Examples](https://github.com/miroapp/app-examples)