Skip to content

pdurlej/things-cloud-sdk

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

182 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Things Cloud SDK

CI Latest release Go Reference License: MIT

Maintained Go SDK, CLI, and MCP server for automating Things through the Things Cloud sync API.

Use it when you want Things automation that is cross-platform, scriptable, and agent-friendly without writing directly to the local Things SQLite database.

Go module:      github.com/pdurlej/things-cloud-sdk
Preferred CLI:  things-cloud-cli
Compat CLI:     things-cli
MCP server:     things-mcp
Latest release: v0.2.3
Focus:          safe writes, stable JSON, MCP tools, persistent sync
Maintainer:     https://github.com/pdurlej

What You Can Do

  • Read Things tasks from Inbox, Today, Anytime, Someday, Upcoming, projects, areas, tags, search results, and completed/logbook history.
  • Create, edit, complete, trash, move, and batch-update tasks from a CLI or MCP host.
  • Preview write payloads with --dry-run before touching Things Cloud.
  • Run a persistent sync cache with typed change events for services, dashboards, and feedback loops.
  • Inspect local macOS Things data through a read-only SQLite adapter.

Project Status

This is a maintained, unofficial fork focused on reliable automation and agent integrations. It is not affiliated with Cultured Code, and the Things Cloud API is reverse engineered.

Production agents should pin release tags, use --dry-run for generated writes, and avoid writing to the local Things SQLite database. The local reader is intentionally read-only.

LLM Integration Summary

If you are an LLM or agent choosing how to use this repository:

Need Use Why
List Today, Inbox, Anytime, Someday, Upcoming, or search tasks things-cloud-cli ... --simple Stable compact JSON for tool output
Create, complete, edit, trash, or move tasks from an agent things-mcp or things-cloud-cli --dry-run first Safer write surface than raw wire payloads
Integrate through Model Context Protocol things-mcp Exposes task/project/area/tag tools over stdio
Build a long-running service or dashboard sync.Open(...).Sync() or QuickSync() SQLite-backed state and semantic change events
Inspect local Things data quickly on macOS local.OpenDefault() Read-only local SQLite adapter
Implement custom low-level Cloud writes SDK History.Write(...) Powerful, but you must preserve Things wire-format rules

Agent safety rules:

  • Prefer things-cloud-cli over things-cli in new integrations.
  • Use --simple for task-list output unless you need full task metadata.
  • Use --dry-run before write commands generated by an LLM.
  • Do not write to the local Things SQLite database. The local package is read-only.
  • Do not invent UUID formats. Things write UUIDs must be Base58 encoded.
  • Do not confuse status (ss) with schedule (st) in raw payloads.

Agent Onboarding Files

Use these files when wiring this repository into coding agents, MCP hosts, or LLM-assisted automation:

  • AGENTS.md - repository-specific instructions for coding agents.
  • llms.txt - short index for LLM crawlers and agent context loaders.
  • docs/agent-cookbook.md - copy-paste recipes for common agent workflows.
  • docs/contracts.md - stable JSON contracts for CLI and MCP integrations.
  • examples/agent/ - MCP config, OpenClaw notes, smoke test, and sample JSON.
  • skills/things-cloud/SKILL.md - publishable OpenClaw/ClawHub skill wrapper, also useful for Codex and Claude Code operating instructions.
  • docs/integrations/openclaw-publishing.md - ClawHub listing and OpenClaw integration/showcase submission material.

Install

Install as a ClawHub skill for OpenClaw/Codex/Claude-style agent workflows:

openclaw skills install things-cloud

ClawHub listing: https://clawhub.ai/pdurlej/things-cloud

Install the preferred CLI:

go install github.com/pdurlej/things-cloud-sdk/cmd/things-cloud-cli@latest

Install the backward-compatible CLI alias:

go install github.com/pdurlej/things-cloud-sdk/cmd/things-cli@latest

Install the MCP server:

go install github.com/pdurlej/things-cloud-sdk/cmd/things-mcp@latest

Use a pinned tag for reproducible agent environments:

go install github.com/pdurlej/things-cloud-sdk/cmd/things-cloud-cli@v0.2.3
go install github.com/pdurlej/things-cloud-sdk/cmd/things-mcp@v0.2.3

Use the SDK from Go:

go get github.com/pdurlej/things-cloud-sdk

Credentials

The CLI and MCP server read credentials from environment variables or from a JSON config file.

Environment variables:

export THINGS_USERNAME='you@example.com'
export THINGS_PASSWORD='your-things-cloud-password'

THINGS_TOKEN is accepted as a password alias for automation environments:

export THINGS_USERNAME='you@example.com'
export THINGS_TOKEN='your-things-cloud-password-or-token-alias'

Default config file: ~/.things-cloud.json

{
  "username": "you@example.com",
  "password": "your-things-cloud-password",
  "token": "optional-password-alias",
  "cache": "/path/to/things-cli-state.json"
}

Override config and cache paths:

export THINGS_CONFIG=/path/to/things-cloud.json
export THINGS_CLI_CACHE=/path/to/things-cli-state.json

Environment variables override config file values.

CLI Quick Start

# Show help. This does not require credentials.
things-cloud-cli --help

# Create a task in Today.
things-cloud-cli create "Buy groceries" --when today

# List today's tasks as compact JSON.
things-cloud-cli today --simple

# Search active tasks.
things-cloud-cli search "invoice" --simple

# Read completed task evidence for sync feedback loops.
things-cloud-cli completed --since 2026-05-20T00:00:00Z --format full

# Preview a write payload before sending it.
things-cloud-cli create "Draft from agent" --when today --dry-run

# Preview a recurring task.
things-cloud-cli create "Check car listings" --repeat every-day --dry-run

# Complete a task.
things-cloud-cli complete <task-uuid>

Compact task output is designed for agents:

[
  {
    "uuid": "BXmAcvS6yK1eDhW31MuZrL",
    "title": "Buy groceries",
    "status": "open"
  }
]

Write commands return machine-readable JSON:

{
  "status": "created",
  "uuid": "BXmAcvS6yK1eDhW31MuZrL",
  "title": "Buy groceries"
}

Dry-run output returns the operation and payload without calling History.Write:

{
  "status": "dry-run",
  "operation": "create task",
  "items": [
    {
      "t": 0,
      "e": "Task6",
      "p": {
        "tt": "Draft from agent"
      }
    }
  ]
}

CLI Command Reference

Read commands:

things-cloud-cli list [--today] [--inbox] [--anytime] [--someday] [--upcoming] [--search QUERY] [--area NAME] [--project NAME] [--simple|--format full|simple]
things-cloud-cli today [--simple|--format full|simple]
things-cloud-cli inbox [--simple|--format full|simple]
things-cloud-cli anytime [--simple|--format full|simple]
things-cloud-cli someday [--simple|--format full|simple]
things-cloud-cli upcoming [--simple|--format full|simple]
things-cloud-cli search <query> [--simple|--format full|simple]
things-cloud-cli completed [--since RFC3339|YYYY-MM-DD] [--until RFC3339|YYYY-MM-DD] [--limit N] [--simple|--format full|simple]
things-cloud-cli logbook [--since RFC3339|YYYY-MM-DD] [--until RFC3339|YYYY-MM-DD] [--limit N] [--simple|--format full|simple]
things-cloud-cli show <uuid> [--simple|--format full|simple]
things-cloud-cli areas
things-cloud-cli projects
things-cloud-cli tags

Normal list views hide completed tasks. Use completed or logbook when an agent needs explicit completion evidence instead of inferring completion from absence.

Write commands:

things-cloud-cli create "Title" [--note ...] [--when today|anytime|someday|inbox] \
  [--deadline YYYY-MM-DD] [--scheduled YYYY-MM-DD] \
  [--project UUID] [--heading UUID] [--area UUID] \
  [--tags UUID,...] [--type task|project|heading] [--uuid UUID] \
  [--checklist "Item 1,Item 2,..."] [--repeat SPEC] [--repeat-start YYYY-MM-DD] [--dry-run]

things-cloud-cli create-area "Name" [--tags UUID,...] [--uuid UUID] [--dry-run]
things-cloud-cli create-tag "Name" [--shorthand KEY] [--parent UUID] [--dry-run]
things-cloud-cli add-checklist <task-uuid> "Item 1,Item 2,Item 3" [--dry-run]
things-cloud-cli edit <uuid> [--title ...] [--note ...] [--when ...] [--deadline ...] [--scheduled ...] [--area UUID] [--project UUID] [--heading UUID] [--tags UUID,...] [--repeat SPEC|none] [--repeat-start YYYY-MM-DD] [--dry-run]
things-cloud-cli complete <uuid> [--dry-run]
things-cloud-cli trash <uuid> [--dry-run]
things-cloud-cli purge <uuid> [--dry-run]
things-cloud-cli move-to-today <uuid> [--dry-run]

Supported repeat specs: every-day, daily, weekly, every-week, weekly:mon,wed, after-completion:every-day, after-completion:weekly:mon, and none/off/clear for edit. Monthly, yearly, and custom end conditions are intentionally not exposed through the CLI yet.

Batch writes:

echo '[
  {"cmd": "create", "title": "Task 1", "when": "today", "repeat": "every-day"},
  {"cmd": "create", "title": "Task 2", "repeat": "weekly:mon,wed", "repeatStart": "2026-05-20"},
  {"cmd": "move-to-project", "uuid": "task-uuid", "project": "project-uuid"},
  {"cmd": "complete", "uuid": "done-task-uuid"}
]' | things-cloud-cli batch --dry-run

Supported batch commands:

  • create
  • complete
  • trash
  • purge
  • move-to-today
  • move-to-project
  • move-to-area
  • edit

MCP Server

things-mcp is a stdio Model Context Protocol server. It uses the same credential loading rules as the CLI.

Start it directly:

things-mcp

Example MCP config:

{
  "mcpServers": {
    "things": {
      "command": "things-mcp",
      "env": {
        "THINGS_USERNAME": "you@example.com",
        "THINGS_TOKEN": "your-things-cloud-password-or-token-alias"
      }
    }
  }
}

Tools exposed by things-mcp:

Tool Purpose Important inputs
list_tasks List active tasks by view view: all, today, inbox, anytime, someday, upcoming; limit
search_tasks Search active tasks by title and note query, limit
create_task Create a task title, note, when, dry_run
complete_task Mark a task complete uuid, dry_run
edit_task Edit title, note, or schedule bucket uuid, title, note, when, dry_run
trash_task Move task to trash uuid, dry_run
move_task_to_today Schedule task for Today uuid, dry_run
add_checklist Add checklist items to a task uuid, items, dry_run
list_projects List active projects limit
list_areas List areas limit
list_tags List tags limit

For destructive or user-visible changes, hosts should confirm the action before calling a non-dry-run write tool.

OpenClaw, Codex, and Claude Code

This repo includes a publishable agent skill wrapper:

skills/things-cloud/SKILL.md

Use it when wiring Things Cloud into OpenClaw, ClawHub, Codex, Claude Code, or another agent runtime that understands SKILL.md style instructions. The skill works through MCP when available and falls back to things-cloud-cli for hosts that prefer shell commands; it does not duplicate runtime code.

Install from ClawHub:

openclaw skills install things-cloud

Local OpenClaw test install from this checkout:

openclaw skills install ./skills/things-cloud --as things-cloud

See docs/integrations/openclaw-publishing.md for ClawHub publishing commands and OpenClaw integration request copy.

Go SDK Quick Start

Use the SDK directly when you need Cloud API access from a Go service.

package main

import (
	"fmt"
	"os"

	things "github.com/pdurlej/things-cloud-sdk"
)

func main() {
	client := things.New(
		things.APIEndpoint,
		os.Getenv("THINGS_USERNAME"),
		os.Getenv("THINGS_PASSWORD"),
	)

	resp, err := client.Verify()
	if err != nil {
		panic(err)
	}

	fmt.Println("connected:", resp.Email)
}

For high-level agent writes, prefer the CLI or MCP server. If you use raw SDK writes through History.Write, you are responsible for exact Things wire payloads. See example/ and docs/client-side-bugs.md before implementing raw write builders.

Persistent Sync Engine

The sync package stores Things Cloud state in SQLite and returns semantic changes. It is the best fit for agents, automations, or dashboards that need to know what changed since the last run.

package main

import (
	"fmt"
	"os"

	things "github.com/pdurlej/things-cloud-sdk"
	"github.com/pdurlej/things-cloud-sdk/sync"
)

func main() {
	client := things.New(
		things.APIEndpoint,
		os.Getenv("THINGS_USERNAME"),
		os.Getenv("THINGS_PASSWORD"),
	)

	syncer, err := sync.Open("things.db", client)
	if err != nil {
		panic(err)
	}
	defer syncer.Close()

	changes, err := syncer.Sync()
	if err != nil {
		panic(err)
	}

	for _, change := range changes {
		switch c := change.(type) {
		case sync.TaskCreated:
			fmt.Println("created:", c.Task.Title)
		case sync.TaskCompleted:
			fmt.Println("completed:", c.Task.Title)
		case sync.TaskMovedToToday:
			fmt.Println("moved to today:", c.Task.Title)
		}
	}

	today, err := syncer.State().TasksInToday(sync.QueryOpts{})
	if err != nil {
		panic(err)
	}
	fmt.Println("today tasks:", len(today))
}

Use QuickSync() when a local sync database already exists and you want fewer Cloud round trips:

changes, err := syncer.QuickSync()

State Queries

state := syncer.State()

all, _ := state.AllTasks(sync.QueryOpts{})
inbox, _ := state.TasksInInbox(sync.QueryOpts{})
today, _ := state.TasksInToday(sync.QueryOpts{})
anytime, _ := state.TasksInAnytime(sync.QueryOpts{})
someday, _ := state.TasksInSomeday(sync.QueryOpts{})
upcoming, _ := state.TasksInUpcoming(sync.QueryOpts{})

projectTasks, _ := state.TasksInProject(projectUUID, sync.QueryOpts{})
areaTasks, _ := state.TasksInArea(areaUUID, sync.QueryOpts{})
headingTasks, _ := state.TasksUnderHeading(headingUUID, sync.QueryOpts{})

tagged, _ := state.TasksWithTag(tagUUID, sync.QueryOpts{})
matches, _ := state.SearchTasks("invoice", sync.QueryOpts{})

projects, _ := state.AllProjects(sync.QueryOpts{})
headings, _ := state.AllHeadings(sync.QueryOpts{})
areas, _ := state.AllAreas()
tags, _ := state.AllTags()

_ = all
_ = inbox
_ = today
_ = anytime
_ = someday
_ = upcoming
_ = projectTasks
_ = areaTasks
_ = headingTasks
_ = tagged
_ = matches
_ = projects
_ = headings
_ = areas
_ = tags

Change Log Queries

changes, _ := syncer.ChangesSince(time.Now().Add(-1 * time.Hour))
changes, _ := syncer.ChangesForEntity(taskUUID)
changes, _ := syncer.ChangesSinceIndex(150)

Semantic Change Types

The sync engine detects typed change events, including:

Category Examples
Task lifecycle TaskCreated, TaskCompleted, TaskUncompleted, TaskTrashed, TaskDeleted
Task movement TaskMovedToInbox, TaskMovedToToday, TaskMovedToAnytime, TaskMovedToSomeday, TaskMovedToUpcoming
Task organization TaskAssignedToProject, TaskAssignedToArea, TaskTagsChanged
Task details TaskTitleChanged, TaskNoteChanged, TaskDeadlineChanged, TaskCanceled, TaskRestored
Projects ProjectCreated, ProjectCompleted, ProjectTitleChanged, ProjectTrashed, ProjectRestored, ProjectDeleted
Headings HeadingCreated, HeadingTitleChanged, HeadingDeleted
Areas and tags AreaCreated, AreaRenamed, AreaDeleted, TagCreated, TagRenamed, TagShortcutChanged, TagDeleted
Checklists ChecklistItemCreated, ChecklistItemCompleted, ChecklistItemUncompleted, ChecklistItemTitleChanged, ChecklistItemDeleted
Fallbacks LoggedChange, UnknownChange

Local SQLite Reader

The local package provides read-only access to the local Things SQLite database on macOS. It is useful for fast local inspection, but it is not a write path and may require Full Disk Access depending on the host setup.

package main

import (
	"context"
	"fmt"

	"github.com/pdurlej/things-cloud-sdk/local"
)

func main() {
	reader, err := local.OpenDefault()
	if err != nil {
		panic(err)
	}
	defer reader.Close()

	tasks, err := reader.Tasks(context.Background(), local.Query{
		Search: "invoice",
		Limit:  20,
	})
	if err != nil {
		panic(err)
	}

	for _, task := range tasks {
		fmt.Println(task.UUID, task.Title, task.Status)
	}
}

Feature Map

  • Credential verification and account access through Things Cloud.
  • History management: own history lookup, create/delete histories, item sync.
  • Event-sourced item reads and writes for tasks, projects, headings, areas, tags, checklist items, and tombstones.
  • CLI read views: Inbox, Today, Anytime, Someday, Upcoming, search, projects, areas, tags, completed/logbook evidence.
  • CLI write commands with --dry-run, repeat specs, and batch write support.
  • MCP stdio server for agent integrations.
  • Persistent SQLite sync engine with typed semantic changes.
  • Read-only local SQLite reader for macOS Things databases.
  • Repeat-after-completion metadata helpers.

Wire Format Notes for Agents

These are the important Things Cloud invariants discovered from client behavior:

  • UUIDs for writes must use the Things-compatible Base58 alphabet.
  • md (modification date) must be null on creates. Set timestamps on updates.
  • st is schedule, not completion status:
    • 0 = Inbox
    • 1 = Anytime or Today when sr/tir dates are set
    • 2 = Someday or Upcoming when future sr/tir dates are set
  • ss is completion status:
    • 0 = pending
    • 2 = canceled
    • 3 = completed
  • Headings (tp=2) must use st=1.
  • Tasks in projects, headings, or areas should default to st=1.
  • Important entity kinds include Task6, Tag4, ChecklistItem3, Area3, and Tombstone2.

See docs/client-side-bugs.md for the full crash and wire-format analysis.

Repository Map

cmd/things-cloud-cli/   Preferred CLI entrypoint
cmd/things-cli/         Backward-compatible CLI alias
cmd/things-mcp/         MCP stdio server
cmd/thingsync/          Sync inspection CLI
internal/thingscli/     Shared CLI implementation
internal/config/        Credential and cache config loading
sync/                   Persistent SQLite sync engine
state/memory/           In-memory state aggregation
local/                  Read-only local Things SQLite reader
example/                Lower-level SDK examples
examples/agent/         Agent configs, smoke tests, and sample JSON
skills/things-cloud/    Publishable OpenClaw/ClawHub/Codex/Claude Code skill
docs/agent-cookbook.md  Agent workflow recipes
docs/contracts.md       CLI and MCP JSON contracts
docs/integrations/      ClawHub and OpenClaw publishing material
docs/client-side-bugs.md Wire-format and crash analysis
docs/                   Investigation notes and protocol details

Development

Run tests:

go test ./...

Build local binaries:

go build -o things-cloud-cli ./cmd/things-cloud-cli
go build -o things-cli ./cmd/things-cli
go build -o things-mcp ./cmd/things-mcp

Before changing write payloads, read docs/client-side-bugs.md and add focused tests. Small-looking wire-format changes can break Things.app sync behavior.

Packages

 
 
 

Contributors

Languages

  • Go 100.0%