{"id":51009921,"url":"https://github.com/superbereza/things-cli","last_synced_at":"2026-06-21T01:07:57.437Z","repository":{"id":362306042,"uuid":"1256700563","full_name":"superbereza/things-cli","owner":"superbereza","description":"CLI for Things 3 (read inbox/today/projects, search, add/complete/update todos) — JSON output, ships an agent skill","archived":false,"fork":false,"pushed_at":"2026-06-12T00:18:26.000Z","size":80,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-12T02:09:04.467Z","etag":null,"topics":["ai-tools","cli","macos","productivity","python","skills","task-management","things3"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/superbereza.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-02T02:39:17.000Z","updated_at":"2026-06-12T00:18:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/superbereza/things-cli","commit_stats":null,"previous_names":["superbereza/things-cli"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/superbereza/things-cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superbereza%2Fthings-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superbereza%2Fthings-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superbereza%2Fthings-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superbereza%2Fthings-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/superbereza","download_url":"https://codeload.github.com/superbereza/things-cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/superbereza%2Fthings-cli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34590381,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-tools","cli","macos","productivity","python","skills","task-management","things3"],"created_at":"2026-06-21T01:07:56.732Z","updated_at":"2026-06-21T01:07:57.423Z","avatar_url":"https://github.com/superbereza.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# things-cli\n\nA small Python CLI for [Things 3](https://culturedcode.com/things/) — read your inbox / today / upcoming / projects / areas / tags, search, add todos and projects, complete, cancel, update, batch-edit. Outputs JSON, designed for piping into `jq` or being driven by an AI agent.\n\nShips an agent **skill** at [`skills/things/SKILL.md`](skills/things/SKILL.md) so an agent can read and update Things tasks automatically — run `things today` proactively at the start of a session and reach for `things add` / `things complete` when the conversation turns to tasks. The same skill is wired up for **Claude Code, Cursor, Codex and Gemini** from one source (see [Install](#install)).\n\n## How it works\n\n- **Reads** go through [`thingsapi/things.py`](https://github.com/thingsapi/things.py), which queries the local Things SQLite database directly (fast, no AppleScript round-trips).\n- **Writes** go through the [Things URL scheme](https://culturedcode.com/things/help/url-scheme/) via `osascript` so Things processes them natively.\n- **Auth token** for `update` / `complete` / `cancel` / `tag-*` is fetched via `things.token()`.\n\nCLI logic is a small Python package (`src/things_cli/`, declared in `pyproject.toml` with a `things` entry point). `bin/things` is a thin self-bootstrapping launcher that creates a dedicated `.venv/`, installs the package editable on first run, and execs the `things` entry point (works whether called directly, via symlink, or from a plugin dir). Both `uv` and stdlib `venv` + `pip` are supported.\n\n## Install\n\nRequirements:\n\n- Python 3.10+\n- macOS (Things 3 is Mac-only)\n- Things 3 with URL scheme enabled — open Things → Settings → General → \"Enable Things URLs\"\n\nInstall via the plugin. The `.venv/` is built automatically on first run.\n\n### 1. As a Claude Code plugin (this repo is its own marketplace)\n\n```text\n/plugin marketplace add superbereza/things-cli\n/plugin install things@things-cli\n```\n\nClaude pulls the repo, loads `skills/things/SKILL.md`, and the first `things …` call builds the venv. The plugin's `bin/` is auto-added to PATH, so the skill just calls `things`.\n\n### 2. From an aggregate marketplace\n\nIf this plugin is listed in a shared marketplace (e.g. `superbereza/superbereza-skills`):\n\n```text\n/plugin marketplace add superbereza/superbereza-skills\n/plugin install things@superbereza-skills\n```\n\n### Other agents\n\nThe same `skills/` directory is exposed to **Cursor** (`.cursor-plugin/`), **Codex** (`.codex-plugin/`) and **Gemini** (`gemini-extension.json` → [`AGENTS.md`](AGENTS.md)). One skill, one source of truth — see [`AGENTS.md`](AGENTS.md).\n\nRun `things doctor --pretty` after install to verify everything's wired up:\n\n```\n  ✓ things.py library        ok           version 1.0.1\n  ✓ Things 3 app             ok           version 3.22.11\n  ✓ SQLite read access       ok           reads work — 65 inbox items\n  ✓ Auth token               ok           22-char token available\n\nOverall: ALL OK\n```\n\n## Usage\n\n### Reads\n\n```bash\nthings today                          # JSON array\nthings today --pretty                 # indented\nthings today --grep \"report\" --limit 5\nthings inbox --grep \"buy\"\nthings projects --items               # include each project's task titles\nthings areas --items                  # include each area's projects\nthings tags\nthings search \"meeting\" --limit 10\n```\n\n| Flag | On which read | Effect |\n|---|---|---|\n| `--pretty`, `-p` | all | Indent JSON |\n| `--grep PATTERN`, `-g` | all except `search` | Case-insensitive substring filter on title |\n| `--limit N` | all | Truncate to N items |\n| `--items`, `-i` | `projects`, `areas` | Include children titles |\n\n### Single writes\n\n```bash\nthings add \"Buy milk\"\nthings add \"Read article\" --notes \"https://...\" --when today --tags reading\nthings add \"Task\" --list \"Work\" --tags \"urgent,important\"\nthings add \"Trip prep\" \\\n    --checklist \"Pack toothbrush\" --checklist \"Charge headphones\"\n\n# Verify the write actually landed and capture the real UUID:\nthings add \"Buy milk\" --when today --tags shopping --wait\n# → {\"status\":\"ok\",\"title\":\"Buy milk\",\"uuid\":\"3aEmFg6pBm1Vihy5DofYdR\"}\n\n# Add a project with initial tasks\nthings add-project \"Renovate kitchen\" --area \"Home\" \\\n    --todos \"Measure walls\" --todos \"Pick paint color\"\n\n# Move / update\nthings update \u003cuuid\u003e --title \"New title\"\nthings update \u003cuuid\u003e --list \"Other Project\"       # move (name OR UUID)\nthings update \u003cuuid\u003e --list MDs59magzJkPhhpPSnZSq  # UUID also works\nthings update \u003cuuid\u003e --when tomorrow --deadline 2026-07-01\n\n# Complete / cancel — both accept multiple UUIDs\nthings complete \u003cuuid1\u003e \u003cuuid2\u003e \u003cuuid3\u003e\nthings cancel \u003cuuid\u003e\n\n# Tags — preserve existing\nthings tag-add \u003cuuid\u003e urgent reviewed\nthings tag-remove \u003cuuid\u003e draft\n\n# Open something in the Things UI\nthings show today\nthings show \u003cuuid\u003e\n```\n\n### Batch writes via `--stdin`\n\n`things add --stdin` accepts either a JSON array or NDJSON (one JSON object per line) — saves N round-trips when adding many items:\n\n```bash\necho '[\n  {\"title\": \"Email Alice\",     \"when\": \"today\", \"tags\": \"comms\"},\n  {\"title\": \"Review PR #1234\", \"when\": \"today\"},\n  {\"title\": \"Pay credit card\", \"deadline\": \"2026-06-15\", \"list\": \"Personal\"}\n]' | things add --stdin --wait --pretty\n```\n\nReturns an array of `{status, title, uuid}` records (`uuid` populated only with `--wait`).\n\nNDJSON form is also accepted:\n\n```bash\n{\"title\": \"T1\"}\n{\"title\": \"T2\", \"deadline\": \"2026-07-01\"}\n{\"title\": \"T3\", \"list\": \"Work\"}\n```\n\nPer-item fields supported (mirror the single-add flags): `title` (required), `notes`, `when`, `deadline`, `tags` (string, comma-separated), `list` (name OR UUID), `checklist` (list of strings).\n\n### Diagnostics\n\n```bash\nthings --version\nthings doctor               # JSON\nthings doctor --pretty      # human-readable table\n```\n\n`doctor` checks: things.py library, Things 3 app reachable, SQLite reads, auth token. Exit code 1 if anything fails.\n\n## Output contract (for agents / scripts)\n\nReads return a JSON **array** of items; the full schema is documented in [skills/things/SKILL.md](skills/things/SKILL.md#output-contract).\n\nCommon todo fields:\n\n```json\n{\n  \"uuid\": \"3aEmFg6pBm1Vihy5DofYdR\",      // 22-char base62, stable\n  \"title\": \"Buy milk\",\n  \"type\": \"to-do\",                        // \"to-do\" | \"project\"\n  \"status\": \"incomplete\",                 // \"incomplete\" | \"completed\" | \"canceled\"\n  \"list\": \"Inbox\",                        // smart-list this todo is parked in\n  \"notes\": \"...\",                         // optional\n  \"start_date\": \"2026-06-02\",             // optional\n  \"deadline\": \"2026-06-10\",               // optional\n  \"tags\": [\"urgent\", \"work\"],             // optional\n  \"project\": \"Work\",                      // optional, resolved name\n  \"project_uuid\": \"...\",                  // optional, paired\n  \"area\": \"Home\",                         // optional, resolved name\n  \"area_uuid\": \"...\"                      // optional, paired\n}\n```\n\nWrites return a status object:\n\n```json\n{ \"status\": \"ok\", \"title\": \"Buy milk\", \"uuid\": \"...\" }\n{ \"status\": \"ok\", \"uuid\": \"...\", \"completed\": true }\n{ \"status\": \"ok\", \"uuid\": \"...\", \"tags\": [\"urgent\", \"reviewed\"] }\n```\n\nErrors are always structured (exit code 1):\n\n```json\n{ \"status\": \"error\", \"code\": \"INVALID_UUID\" | \"MISSING_TITLE\" | \"NOT_FOUND\" | ..., \"message\": \"...\" }\n```\n\n## Subcommand reference\n\n| Command | Action | Needs `things.py` lib |\n|---|---|---|\n| `inbox`, `today`, `upcoming`, `anytime`, `someday` | List todos in that smart list | yes |\n| `projects [--items]` | List projects (and their tasks) | yes |\n| `areas [--items]` | List areas (and their projects) | yes |\n| `tags` | List all tags | yes |\n| `search \u003cquery\u003e` | Substring search | yes |\n| `add \u003ctitle\u003e [...]` | Add a todo | no (URL scheme) |\n| `add --stdin` | Add many todos (JSON array or NDJSON) | no |\n| `add-project \u003ctitle\u003e [...]` | Add a project | no |\n| `update \u003cuuid\u003e [...]` | Update existing todo (incl. move via `--list`) | yes (auth token) |\n| `complete \u003cuuid\u003e [\u003cuuid\u003e...]` | Mark complete (✓) | yes (auth token) |\n| `cancel \u003cuuid\u003e [\u003cuuid\u003e...]` | Mark canceled (✗, won't do) | yes (auth token) |\n| `tag-add \u003cuuid\u003e \u003ctag\u003e...` | Add tags, preserve existing | yes |\n| `tag-remove \u003cuuid\u003e \u003ctag\u003e...` | Remove specific tags, keep the rest | yes |\n| `show \u003cid\\|inbox\\|today\\|...\u003e` | Open in Things app UI | no |\n| `doctor` | Self-check installation | mostly no |\n\nRun `things \u003ccommand\u003e --help` for per-command flags.\n\n## Caveats\n\n- **Writes are fire-and-forget** unless you pass `--wait`. The CLI returns `{\"status\":\"ok\"}` as soon as the URL is dispatched; if Things rejects the URL, the CLI never sees the error. `--wait` polls SQLite for up to ~3 s to confirm.\n- **No `trash`** subcommand — Things' URL scheme can't delete. `cancel` marks \"won't do\" (✗), `complete` marks done (✓). Real deletion is GUI-only.\n- **Tag mutations** through Things' URL scheme always replace the full set; `tag-add` / `tag-remove` work around this by reading existing tags first via `things.py` and sending the merged set.\n- **UUIDs are 22-char base62.** The validator on `complete` / `cancel` / `update` / `tag-*` rejects anything else with a clear `INVALID_UUID` error — usually the cause is forgetting to leave a shell variable unquoted: use `things complete $uuids`, not `things complete \"$uuids\"`.\n- **`--grep` filters in Python** after fetching everything from SQLite. Fine for the typical Things database (hundreds of items), not a substitute for SQL pushdown on huge collections.\n\n## The skill\n\nThe skill lives at `skills/things/SKILL.md` — the single source of truth, shared\nacross agents:\n\n- **Claude Code** — the plugin install exposes it. New sessions pick it up (the\n  current one does not — skills load at session start).\n- **Cursor / Codex** — `.cursor-plugin/plugin.json` and `.codex-plugin/plugin.json`\n  point at the same `./skills/`.\n- **Gemini** — reads [`GEMINI.md`](GEMINI.md) (declared in `gemini-extension.json`).\n\nThe skill description tells the agent to use `things` whenever the user mentions\ntasks/today/inbox/etc., and to run `things today` proactively at the start of a\nsession.\n\n## Uninstall\n\n`/plugin uninstall things@things-cli`. (If you cloned the repo, just delete the clone — its `.venv/` lives inside it.)\n\n## Credits\n\nCLI logic ported from [hald/things-mcp](https://github.com/hald/things-mcp) (an MCP server for Things 3). Uses [thingsapi/things.py](https://github.com/thingsapi/things.py) for SQLite reads.\n\n## OpenCode\n\nThis skill also supports [OpenCode](https://opencode.ai) — see [`.opencode/INSTALL.md`](.opencode/INSTALL.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuperbereza%2Fthings-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsuperbereza%2Fthings-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuperbereza%2Fthings-cli/lists"}