{"id":49557060,"url":"https://github.com/ryanlewis/things-cli","last_synced_at":"2026-06-13T19:01:06.827Z","repository":{"id":353223979,"uuid":"1209712080","full_name":"ryanlewis/things-cli","owner":"ryanlewis","description":"Command-line interface for Things3 on macOS.","archived":false,"fork":false,"pushed_at":"2026-06-11T18:43:28.000Z","size":372,"stargazers_count":2,"open_issues_count":9,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-11T20:15:49.404Z","etag":null,"topics":["cli","golang","macos","productivity","task-management","things3"],"latest_commit_sha":null,"homepage":"https://things.rlew.io","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ryanlewis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":null,"dco":null,"cla":null}},"created_at":"2026-04-13T17:51:05.000Z","updated_at":"2026-05-30T15:56:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ryanlewis/things-cli","commit_stats":null,"previous_names":["ryanlewis/things-cli"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/ryanlewis/things-cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanlewis%2Fthings-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanlewis%2Fthings-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanlewis%2Fthings-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanlewis%2Fthings-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ryanlewis","download_url":"https://codeload.github.com/ryanlewis/things-cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanlewis%2Fthings-cli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34296383,"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-13T02:00:06.617Z","response_time":62,"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":["cli","golang","macos","productivity","task-management","things3"],"created_at":"2026-05-03T05:00:52.679Z","updated_at":"2026-06-13T19:01:06.812Z","avatar_url":"https://github.com/ryanlewis.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# things-cli\n\n[Website](https://things.rlew.io) ·\n[Releases](https://github.com/ryanlewis/things-cli/releases/latest) ·\n[Issues](https://github.com/ryanlewis/things-cli/issues)\n\nA small Go CLI for [Things3](https://culturedcode.com/things/) on macOS. Reads\ntasks, projects, areas and tags straight from the Things3 SQLite database\n(read-only) and writes via the `things:///` URL scheme and AppleScript — so the\napp stays the source of truth and your data never leaves the machine.\n\n```sh\nthings                                 # today's tasks\nthings inbox -j | jq                   # JSON for piping into anything\nthings add \"Buy milk\" --when today --tags errand\nthings edit 3 --add-tags urgent --deadline 2026-05-01\nthings complete \"Pay rent\"             # by title, with interactive disambig\nthings search migrate                  # full-text across titles + notes\nthings open --project \"Launch\"         # reveal in the Things app\n```\n\nWhat it does:\n\n- **List \u0026 inspect** every built-in view (`today`, `inbox`, `upcoming`,\n  `anytime`, `someday`, `logbook`, `trash`, `deadlines`) plus projects,\n  areas, tags, and full-text search\n- **Create** tasks and projects with notes, schedules, deadlines, tags,\n  checklists, and headings\n- **Edit** anything mutable via `things:///update` — only the flags you\n  pass are sent, so unset fields stay untouched\n- **Complete, cancel, log**, or **reveal** items in the app\n- **Import** [Things JSON URL scheme](https://culturedcode.com/things/support/articles/2803573/)\n  payloads in bulk\n- **JSON everywhere** — every command supports `-j` / `--json` for clean\n  piping into `jq`, agents, or scripts\n\n**Teach your agent to drive it.** A bundled skill ships in the binary —\ninstall it once and your agent knows when to reach for `things` instead\nof guessing at AppleScript:\n\n```sh\nthings skill install claude            # also: codex, pi\n```\n\nFor other agents, `things skill show` prints the neutral source so you can\nappend it to whatever your agent reads for instructions (e.g. a project\n`AGENTS.md`).\n\n## CLI\n\nBy default output is plain text formatted for humans. Pass `-j` / `--json`\nfor structured JSON suitable for piping into `jq` or another tool. List\ncommands assign each result a stable index (`1`, `2`, `3`, …) you can use\nin follow-up commands like `show`, `edit`, `complete`, and `cancel`.\n\n### Global flags\n\n| Flag | Description | Default |\n| --- | --- | --- |\n| `-j, --json` | Output as JSON instead of plain text | `false` |\n| `--db PATH` | Override the Things3 SQLite database path | auto-detected |\n| `-v, --version` | Print version, commit, and build date and exit (same as `things version`) | — |\n\n### Listing tasks\n\n`things \u003cview\u003e` prints a built-in list. With no arguments, `things` prints\n`today`. View names take precedence over project names — a non-view\nargument is treated as a project name (`things \"Weekly Review\"`), so a\nproject literally called `Inbox` would need `things -p Inbox`.\n\n| View | Description |\n| --- | --- |\n| `today` | Tasks scheduled for today (default) |\n| `inbox` | Inbox |\n| `upcoming` | Scheduled tasks and deadlines |\n| `anytime` | Anytime list |\n| `someday` | Someday list |\n| `logbook` | Completed tasks |\n| `trash` | Trashed tasks |\n| `deadlines` | Tasks with a deadline |\n\nFilters (combine freely with any view):\n\n| Flag | Description |\n| --- | --- |\n| `-p, --project NAME` | Filter by project name or UUID |\n| `-a, --area NAME` | Filter by area name or UUID |\n| `-t, --tag NAME` | Filter by tag name |\n\nExamples:\n\n```sh\nthings                            # today (default)\nthings inbox\nthings upcoming -t urgent\nthings \"Weekly Review\"            # tasks in a project by name\nthings -a Work                    # tasks in an area\n```\n\nOutput groups by project or area; numeric indices are stable for follow-up\ncommands until the next listing:\n\n```text\n$ things\n    Launch v2\n1.  [ ]  Draft release notes      [docs]      today\n2.  [ ]  Cut RC build                          due:2026-04-30\n\n    Errands\n3.  [ ]  Buy milk                 [shopping]\n4.  [x]  Pick up dry cleaning\n```\n\n### Inspecting tasks, projects, areas, tags\n\n| Command | Description |\n| --- | --- |\n| `things show \u003ctask\u003e` | Show a task's detail (with checklist) |\n| `things projects [--area NAME] [--completed]` | List projects |\n| `things areas` | List areas |\n| `things tags` | List tags |\n| `things search \u003cquery\u003e` | Full-text search across titles and notes |\n\n`\u003ctask\u003e` accepts a UUID, a numeric index from the last list, or a title\nsubstring. When a title matches multiple tasks, an interactive prompt picks\nbetween them; non-TTY callers get the match list as an error.\n\n```sh\nthings show 3                     # task #3 from the last list\nthings show \"Pay rent\"            # by title (interactive disambig)\nthings search migrate             # full-text search\n```\n\n```text\n$ things show 2\nTitle:    Cut RC build\nUUID:     8K3FpQ2eRtNbHwpNiM71Eu\nStatus:   Open\nProject:  Launch v2\nTags:     release\nDeadline: 2026-04-30\nCreated:  2026-04-12 09:14\nNotes:\n  Coordinate with marketing before tagging.\nChecklist:\n  [x] Bump version\n  [ ] Update changelog\n  [ ] Tag and push\n```\n\n`things projects` renders a one-line-per-project list; the leading glyph\nshows completion progress (`○` empty, `◔ ◑ ◕` partial, `●` done, `◌`\ncancelled):\n\n```text\n$ things projects\n◑  Launch v2          Work\n◔  Migrate API        Work\n○  Garden plan        Home\n●  Spring cleaning    Home\n```\n\n### Creating tasks and projects\n\n`things add \u003ctitle\u003e` creates a task. `things project add \u003ctitle\u003e` creates a\nproject.\n\n| Flag | `add` | `project add` | Description |\n| --- | --- | --- | --- |\n| `--notes TEXT` | ✓ | ✓ | Free-form notes |\n| `--when VALUE` | ✓ | ✓ | Schedule (see [Date values](#date-values)) |\n| `--deadline DATE` | ✓ | ✓ | Deadline date |\n| `--tags LIST` | ✓ | ✓ | Comma-separated tags |\n| `--checklist ITEMS` | ✓ | — | Newline-separated checklist items |\n| `--todos ITEMS` | — | ✓ | Newline-separated initial to-dos |\n| `--project NAME` | ✓ | — | Project to add the task into |\n| `--heading NAME` | ✓ | — | Heading within the project |\n| `--list NAME` | ✓ | — | List (project or area) name |\n| `--area NAME` | — | ✓ | Area to file the project under |\n\nExamples:\n\n```sh\nthings add \"Buy milk\" --when today --tags errand,shopping\nthings add \"Ship v2\" --project \"Launch\" --deadline 2026-04-30\nthings project add \"Launch site\" --area Work --deadline 2026-05-01\n```\n\n### Editing tasks and projects\n\n`things edit \u003ctask\u003e` updates a task via `things:///update`.\n`things project edit \u003cproject\u003e` updates a project via\n`things:///update-project`. Only the flags you pass are sent — unset fields\nstay untouched. An empty value clears the field (e.g. `--deadline \"\"`).\n\n\u003e **Prerequisite:** `edit`, `project edit`, and `import` payloads with\n\u003e `operation: update` require the Things auth token. Enable it once via\n\u003e *Things → Settings → General → Enable Things URLs*. Without it, writes fail\n\u003e with `update: auth token is required — enable Things URLs in Things → Settings → General …`.\n\n| Flag | `edit` | `project edit` | Description |\n| --- | --- | --- | --- |\n| `--title TEXT` | ✓ | ✓ | Replace title |\n| `--notes TEXT` | ✓ | ✓ | Replace notes |\n| `--prepend-notes TEXT` | ✓ | ✓ | Prepend to notes |\n| `--append-notes TEXT` | ✓ | ✓ | Append to notes |\n| `--when VALUE` | ✓ | ✓ | Reschedule (see [Date values](#date-values)) |\n| `--deadline DATE` | ✓ | ✓ | Set deadline |\n| `--tags LIST` | ✓ | ✓ | Replace all tags (comma-separated) |\n| `--add-tags LIST` | ✓ | ✓ | Add tags without replacing existing |\n| `--checklist ITEMS` | ✓ | — | Replace checklist (newline-separated) |\n| `--prepend-checklist ITEMS` | ✓ | — | Prepend checklist items |\n| `--append-checklist ITEMS` | ✓ | — | Append checklist items |\n| `--list NAME` | ✓ | — | Move to list/project by name |\n| `--list-id UUID` | ✓ | — | Move to list/project by UUID |\n| `--heading NAME` | ✓ | — | Set heading within project by name |\n| `--heading-id UUID` | ✓ | — | Set heading within project by UUID |\n| `--area NAME` | — | ✓ | Move project to area by name |\n| `--area-id UUID` | — | ✓ | Move project to area by UUID |\n| `--complete` | ✓ | ✓ | Mark as completed |\n| `--cancel` | ✓ | ✓ | Mark as canceled |\n| `--duplicate` | ✓ | ✓ | Duplicate before applying edits |\n| `--reveal` | ✓ | ✓ | Reveal in Things after editing |\n\nExamples:\n\n```sh\nthings edit 3 --title \"New title\" --when tomorrow\nthings edit \"Buy milk\" --add-tags urgent --deadline 2026-05-01\nthings edit \"Old idea\" --deadline \"\"              # clear the deadline\nthings project edit \"Launch\" --append-notes \"Beta cut on Friday\"\n```\n\n### Completing, cancelling, logging\n\n| Command | Description |\n| --- | --- |\n| `things complete \u003ctask\u003e` | Mark a task or project as completed (project completion is confirmed interactively) |\n| `things cancel \u003ctask\u003e` | Cancel a task |\n| `things log` | Move today's done/cancelled items to the Logbook (Items → Log Completed) |\n\n`log` is the housekeeping action; `logbook` (above) is the *view* of\nalready-archived tasks.\n\n```sh\nthings complete 3\nthings cancel \"Old idea\"\nthings log\n```\n\n### Revealing items in Things3\n\n`things open` brings Things3 forward and reveals a list, item, or quick-find\nresult. Pass exactly one of:\n\n| Flag / Argument | Description |\n| --- | --- |\n| `\u003cref\u003e` | Built-in list name (`today`, `inbox`, …), task UUID, numeric list index, or title |\n| `-p, --project NAME` | Open a project by name or UUID |\n| `-a, --area NAME` | Open an area by name or UUID |\n| `-t, --tag NAME` | Open a tag by name or UUID |\n| `-q, --query TEXT` | App-side quick find |\n\nAdditional flags:\n\n| Flag | Description |\n| --- | --- |\n| `--filter TAGS` | Tag filter on the shown list (comma-separated) |\n| `--background` | Don't bring Things to the foreground |\n\nExamples:\n\n```sh\nthings open today\nthings open \"Pay rent\"\nthings open --project \"Launch\"\nthings open --query staging\n```\n\n### Importing JSON payloads\n\n`things import` forwards a [Things JSON URL scheme\npayload](https://culturedcode.com/things/support/articles/2803573/) — a\nbatch of `to-do`, `project`, `heading`, and `checklist-item` items, each\nwith `operation` and `attributes`. The CLI validates the payload is\nsyntactically valid JSON, then forwards it verbatim. The auth token is\nattached automatically (required for `operation: update` items, harmless\nfor create-only payloads).\n\n| Flag | Description |\n| --- | --- |\n| `-f, --file PATH` | Read JSON payload from this file instead of stdin |\n| `--reveal` | Reveal the first created/updated item in Things after import |\n\n```sh\nthings import \u003c payload.json\nthings import --file payload.json --reveal\n```\n\nNote: macOS `open` has a URL length limit; split very large payloads.\n\n### Date values\n\n`--when` accepts:\n\n| Form | Example |\n| --- | --- |\n| Keyword | `today`, `tomorrow`, `evening`, `anytime`, `someday` |\n| Date | `2026-05-01` |\n| Time | `HH:MM` (`21:30`) or `H:MMam` / `H:MMpm` (`9:30PM`) |\n| Date + time | `2026-05-01@09:30` |\n| RFC3339 | `2026-05-01T09:30:00Z` (rewritten to `YYYY-MM-DD@HH:MM`; offset preserved as wall-clock, no conversion to local time) |\n| Natural language | `friday`, `next monday` (English locales only; passed through verbatim) |\n\nInputs within edit distance 2 of a known keyword are rejected client-side\nas likely typos (e.g. `tommorrow`, `evning`) with a \"did you mean\" hint.\n\n`--deadline` accepts a `YYYY-MM-DD` date or an English natural-language\nphrase. Keywords are not accepted.\n\n`project add` accepts `--notes`, `--when`, `--deadline`, `--tags`, `--area`\nand `--todos` (newline-separated initial to-dos).\n\n`import` accepts a JSON array on stdin (or via `--file`) matching the\n[Things JSON URL scheme payload](https://culturedcode.com/things/support/articles/2803573/)\n— a batch of `to-do`, `project`, `heading`, and `checklist-item` items, each\nwith `operation` and `attributes`. The CLI validates the payload is\nsyntactically valid JSON, then forwards it verbatim. The auth token is\nattached automatically (required for `operation: update` items, harmless for\ncreate-only payloads). Pass `--reveal` to jump to the first created item.\nNote: macOS `open` has a URL length limit; split very large payloads.\n\n`project edit` updates an existing project via the `things:///update-project`\nURL scheme. Only flags you pass are sent. Supported flags: `--title`,\n`--notes`, `--prepend-notes`, `--append-notes`, `--when`, `--deadline`,\n`--tags` (replace), `--add-tags`, `--area` / `--area-id`, `--complete`,\n`--cancel`, `--duplicate`, `--reveal`. An empty value clears the field\n(e.g. `--deadline \"\"`). Requires the Things auth token, same as `edit`.\n\n`edit` updates an existing task via the `things:///update` URL scheme. Only\nflags you pass are sent, so unset fields stay untouched. Supported flags:\n`--title`, `--notes`, `--prepend-notes`, `--append-notes`, `--when`,\n`--deadline`, `--tags` (replace), `--add-tags`, `--checklist`,\n`--prepend-checklist`, `--append-checklist`, `--list` / `--list-id`,\n`--heading` / `--heading-id`, `--complete`, `--cancel`, `--duplicate`,\n`--reveal`. An empty value clears the field (e.g. `--deadline \"\"`). Requires\nthe Things auth token — enable *Things → Settings → General → Enable Things\nURLs*.\n\n### Shell completions\n\n`things completions \u003cbash|zsh|fish\u003e` prints a completion script for the named\nshell. The script delegates back to `things` at completion time, so it never\ngoes stale as the command surface changes — `things \u003cTAB\u003e` completes\nsubcommands and flag names, and a flag's values complete once you've typed it\n(`things list --color \u003cTAB\u003e` → `auto`, `always`, `never`).\n\nThe Homebrew cask generates these on install, so cask users get `things \u003cTAB\u003e`\nwith no extra steps. On every other install path, load the script yourself.\nCompletion shells out to `things` by name, so it works as long as `things` is on\nyour `PATH` (the Homebrew, `go install`, and `make install` paths all put it\nthere):\n\n```sh\n# bash — add to ~/.bashrc (complete -C is a bash builtin; no extra package needed)\nsource \u003c(things completions bash)\n\n# zsh — add to ~/.zshrc, after compinit runs (the stub's bashcompinit needs compdef)\nsource \u003c(things completions zsh)\n\n# fish — load now, and/or persist for new shells\nthings completions fish | source\nmkdir -p ~/.config/fish/completions\nthings completions fish \u003e ~/.config/fish/completions/things.fish\n```\n\nCompletion runs entirely from the static command tree — it never reads the\nThings database, so project, area, and tag *names* are not (yet) completed.\n\n## Agent skill\n\n`things-cli` bundles an agent skill that teaches Claude Code, OpenAI's Codex\nCLI, the Pi coding agent, and other compatible agents how to drive the CLI.\nInstall it once and the agent will know when to reach for `things` instead\nof guessing.\n\n| Command | Description |\n| --- | --- |\n| `things skill list` | Show supported agents and install status |\n| `things skill install \u003cagent\u003e` | Install the skill for an agent (`claude`, `codex`, `pi`) |\n| `things skill uninstall \u003cagent\u003e` | Remove the installed skill |\n| `things skill show` | Print the neutral skill source |\n| `things skill show \u003cagent\u003e` | Print the files that would be installed for that agent |\n\nDefault install paths:\n\n| Agent | Path |\n| --- | --- |\n| `claude` | `~/.claude/skills/things-cli/` |\n| `codex` | `~/.codex/skills/things-cli/` |\n| `pi` | `~/.pi/agent/skills/things-cli/` |\n\n`install` and `uninstall` accept:\n\n| Flag | Description |\n| --- | --- |\n| `--path DIR` | Install or uninstall under a custom directory (e.g. project-local `.claude/skills/` or `.agents/skills/`) |\n| `-y, --yes` | Skip the overwrite/removal prompt |\n\nThe skill body is [`internal/skill/SKILL.md`](internal/skill/SKILL.md),\nembedded in the binary — so a plain `things` upgrade refreshes it; re-run\n`skill install` to pick up the new version.\n\n## How it works\n\n- **Reads** go through `modernc.org/sqlite` (pure Go, no cgo) with\n  `PRAGMA query_only = ON`, so the CLI cannot mutate the Things database.\n- **Writes** go through the official `things:///add` and `things:///update`\n  URL schemes for creating and editing tasks, and through AppleScript for\n  completing and cancelling them. This is the same interface Things exposes\n  to Shortcuts and automation tools.\n- **Task resolution** accepts a UUID, a title (with interactive\n  disambiguation when multiple tasks match) or a numeric index into the last\n  listing.\n\n## Install\n\nWith Homebrew:\n\n```sh\nbrew install ryanlewis/tap/things\n```\n\nOr one-line install (downloads the latest release, verifies checksums,\ninstalls to `/usr/local/bin`):\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/ryanlewis/things-cli/main/install.sh | sh\n```\n\nOverride the destination with `INSTALL_DIR` or pin a version with `VERSION`:\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/ryanlewis/things-cli/main/install.sh \\\n  | INSTALL_DIR=\"$HOME/bin\" VERSION=v0.1.0 sh\n```\n\nOr download a prebuilt binary manually from the\n[latest release](https://github.com/ryanlewis/things-cli/releases/latest)\n(`darwin_arm64` for Apple Silicon, `darwin_amd64` for Intel):\n\n```sh\ntar -xzf things_*_darwin_arm64.tar.gz\nmv things /usr/local/bin/   # or ~/bin, etc.\nthings version\n```\n\nOr install with `go install`:\n\n```sh\ngo install github.com/ryanlewis/things-cli/cmd/things@latest\n```\n\nOr build from source:\n\n```sh\nmake build          # produces ./things\n# or\ngo build -o things ./cmd/things\n```\n\nRequires macOS with Things3 installed. Go 1.26 or later when building from\nsource.\n\n## Project structure\n\n```\ncmd/things/             CLI entry point (alecthomas/kong)\ninternal/model/         Shared types + date codecs (ThingsDate, Core Data time)\ninternal/db/            SQLite queries, read-only\ninternal/things/        URL scheme + AppleScript writers\ninternal/output/        JSON and plain-text rendering\ninternal/cache/         Last-list UUID cache for numeric references\n```\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanlewis%2Fthings-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fryanlewis%2Fthings-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanlewis%2Fthings-cli/lists"}