thingscloud

package module
v0.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 24, 2026 License: MIT Imports: 12 Imported by: 0

README

Things Cloud SDK

Go

Unofficial Go SDK, CLI, and MCP server for the Things Cloud sync API used by Things.

This maintained fork is optimized for automation and agent workflows, especially OpenClaw-style integrations that need predictable reads, safe writes, compact JSON, and a stable Things Cloud module path.

Go module:      github.com/pdurlej/things-cloud-sdk
Preferred CLI:  things-cloud-cli
Compat CLI:     things-cli
MCP server:     things-mcp
Current use:    cloud API reads/writes, persistent sync, local read-only SQLite
Maintainer:     https://github.com/pdurlej

The API is reverse engineered. Treat write paths carefully, use dry runs for agent-generated changes, and pin release tags in production agent environments.

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.

Install

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.

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
docs/agent-cookbook.md  Agent workflow recipes
docs/contracts.md       CLI and MCP JSON contracts
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.

Project Status

This is not an official Cultured Code API. All requests and payloads are based on reverse engineering. Contributions should preserve the automation-friendly contract: stable JSON, safe dry-run behavior, typed sync changes, and explicit separation between Cloud writes and local read-only inspection.

Documentation

Index

Examples

Constants

View Source
const (
	// APIEndpoint is the public culturedcode https endpoint
	APIEndpoint = "https://cloud.culturedcode.com"
)
View Source
const NoteTypeDelta = 2

NoteTypeDelta indicates a note with incremental patches

View Source
const NoteTypeFullText = 1

NoteTypeFullText indicates a note with complete text

View Source
const ThingsUserAgent = "ThingsMac/32209501"

ThingsUserAgent is the http user-agent header set by things for mac

Variables

View Source
var (
	// ErrUnauthorized is returned by the API when the credentials are wrong
	ErrUnauthorized = errors.New("unauthorized")
)

Functions

func ApplyPatches added in v0.2.1

func ApplyPatches(original string, patches []NotePatch) string

ApplyPatches applies a series of text patches to an original string

func String added in v0.2.1

func String(str string) *string

String returns a pointer to a string

Types

type AccountService

type AccountService service

AccountService allows account specific interaction with thingscloud

func (*AccountService) AcceptSLA added in v0.2.1

func (s *AccountService) AcceptSLA() error

func (*AccountService) ChangePassword

func (s *AccountService) ChangePassword(newPassword string) (*Client, error)

ChangePassword allows you to change your account password. Because things does not work with sessions you need to create a new client instance after executing this method

func (*AccountService) Confirm

func (s *AccountService) Confirm(code string) error

Confirm finishes the account creation by providing the email token send by thingscloud

func (*AccountService) Delete

func (s *AccountService) Delete() error

Delete deletes your current thingscloud account. This cannot be reversed

func (*AccountService) SignUp

func (s *AccountService) SignUp(email, password string) (*Client, error)

SignUp creates a new thingscloud account and returns a configured client

type AccountStatus

type AccountStatus string

AccountStatus describes possible thingscloud account statuses

const (
	// AccountStatusActive is for active accounts
	AccountStatusActive AccountStatus = "SYAccountStatusActive"
)

type AppInstanceRequest added in v0.2.1

type AppInstanceRequest struct {
	AppInstanceID string `json:"-"`
	HistoryKey    string `json:"history-key"`
	APNSToken     string `json:"apns-token"`
	AppID         string `json:"app-id"`
	Dev           bool   `json:"dev"`
}

AppInstanceRequest describes the payload for registering a device for push notifications

type Area

type Area struct {
	UUID  string
	Title string
	Tags  []*Tag
	Tasks []*Task
}

Area describes an Area inside things. An Area is a container for tasks 0|uuid|TEXT|0||1 1|title|TEXT|0||0 2|visible|INTEGER|0||0 3|index|INTEGER|0||0

type AreaActionItem

type AreaActionItem struct {
	Item
	P AreaActionItemPayload `json:"p"`
}

AreaActionItem describes an event on an Area

func (AreaActionItem) UUID

func (item AreaActionItem) UUID() string

UUID returns the UUID of the modified Area

type AreaActionItemPayload

type AreaActionItemPayload struct {
	IX     *int     `json:"ix,omitempty"`
	Title  *string  `json:"tt,omitempty"`
	TagIDs []string `json:"tg,omitempty"`
}

AreaActionItemPayload describes the payload for modifying Areas

type Boolean

type Boolean bool

Boolean allows integers to be parsed into booleans, where 1 means true and 0 means false

func (*Boolean) MarshalJSON

func (b *Boolean) MarshalJSON() ([]byte, error)

MarshalJSON takes a boolean and serializes it as an integer

func (*Boolean) UnmarshalJSON

func (b *Boolean) UnmarshalJSON(bs []byte) error

UnmarshalJSON takes an int and creates a boolean instance

type CheckListActionItem

type CheckListActionItem struct {
	Item
	P CheckListActionItemPayload `json:"p"`
}

CheckListActionItem describes an event on a check list item

func (CheckListActionItem) UUID

func (item CheckListActionItem) UUID() string

UUID returns the UUID of the modified CheckListItem

type CheckListActionItemPayload

type CheckListActionItemPayload struct {
	CreationDate     *Timestamp      `json:"cd,omitempty"`
	ModificationDate *Timestamp      `json:"md,omitempty"`
	Index            *int            `json:"ix"`
	Status           *TaskStatus     `json:"ss,omitempty"`
	Title            *string         `json:"tt,omitempty"`
	CompletionDate   *Timestamp      `json:"sp,omitempty"`
	TaskIDs          *[]string       `json:"ts,omitempty"`
	Leavable         *bool           `json:"lt,omitempty"`
	ExtensionData    json.RawMessage `json:"xx,omitempty"`
}

CheckListActionItemPayload describes the payload for modifying CheckListItems

type CheckListItem

type CheckListItem struct {
	UUID             string
	CreationDate     time.Time
	ModificationDate *time.Time
	Status           TaskStatus
	Title            string
	Index            int
	CompletionDate   *time.Time
	TaskIDs          []string
}

CheckListItem describes a check list item 0|uuid|TEXT|0||1 1|userModificationDate|REAL|0||0 2|creationDate|REAL|0||0 3|title|TEXT|0||0 4|status|INTEGER|0||0 5|stopDate|REAL|0||0 6|index|INTEGER|0||0 7|task|TEXT|0||0

type Client

type Client struct {
	Endpoint string
	EMail    string

	ClientInfo ClientInfo
	Debug      bool

	Accounts *AccountService
	// contains filtered or unexported fields
}

Client is a culturedcode cloud client. It can be used to interact with the things cloud to manage your data.

func New

func New(endpoint, email, password string) *Client

New initializes a things client

func (*Client) CreateHistory

func (c *Client) CreateHistory() (*History, error)

CreateHistory requests a new history key

func (*Client) Histories

func (c *Client) Histories() ([]*History, error)

Histories requests all known history keys

Example
client := New(APIEndpoint, os.Getenv("THINGS_USERNAME"), os.Getenv("THINGS_PASSWORD"))

histories, err := client.Histories()
if err != nil {
	log.Printf("Failed loading histories: %q", err.Error())
}

for _, history := range histories {
	if err := history.Sync(); err != nil {
		log.Printf("Failed syncing history: %q", err.Error())
	}
}

func (*Client) History added in v0.1.1

func (c *Client) History(id string) (*History, error)

History requests a specific history

func (*Client) HistoryWithID added in v0.2.1

func (c *Client) HistoryWithID(id string) *History

HistoryWithID creates a History object with the given ID without making a network call. Use this when you already know the history ID (e.g., from a previous sync).

func (*Client) OwnHistory added in v0.2.1

func (c *Client) OwnHistory() (*History, error)

OwnHistory returns the clients own history

func (*Client) RegisterAppInstance added in v0.2.1

func (c *Client) RegisterAppInstance(req AppInstanceRequest) error

RegisterAppInstance registers a device for push notifications via APNS

func (*Client) Verify

func (c *Client) Verify() (*VerifyResponse, error)

Verify checks that the provided API credentials are valid.

Example
client := New(APIEndpoint, os.Getenv("THINGS_USERNAME"), os.Getenv("THINGS_PASSWORD"))

_, err := client.Verify()
if err != nil {
	log.Printf("Invalid Credentials: %q", err.Error())
}

type ClientInfo added in v0.2.1

type ClientInfo struct {
	DeviceModel string `json:"dm"`
	LocalRegion string `json:"lr"`
	NF          bool   `json:"nf"`
	NK          bool   `json:"nk"`
	AppName     string `json:"nn"`
	AppVersion  string `json:"nv"`
	OSName      string `json:"on"`
	OSVersion   string `json:"ov"`
	PrimaryLang string `json:"pl"`
	UserLocale  string `json:"ul"`
}

ClientInfo represents the device metadata sent in the things-client-info header.

func DefaultClientInfo added in v0.2.1

func DefaultClientInfo() ClientInfo

DefaultClientInfo returns a ClientInfo with default values matching a typical Mac client.

type FrequencyUnit added in v0.1.1

type FrequencyUnit int64

FrequencyUnit describes recurring frequencies

var (
	// FrequencyUnitDaily occurs every n days
	FrequencyUnitDaily FrequencyUnit = 16
	// FrequencyUnitWeekly occurs every n weeks
	FrequencyUnitWeekly FrequencyUnit = 256
	// FrequencyUnitMonthly occurs every n months
	FrequencyUnitMonthly FrequencyUnit = 8
	// FrequencyUnitYearly occurs every n years
	FrequencyUnitYearly FrequencyUnit = 4
)

type History

type History struct {
	ID                     string
	Client                 *Client
	LatestServerIndex      int
	LoadedServerIndex      int
	LatestSchemaVersion    int
	EndTotalContentSize    int
	LatestTotalContentSize int
}

History represents a synchronization stream. It's identified with a uuid v4

func (*History) Delete

func (h *History) Delete() error

Delete destroys a history Note that thingscloud will always return 202, even if the key is unknown

func (*History) Items

func (h *History) Items(opts ItemsOptions) ([]Item, bool, error)

Items fetches changes from thingscloud. Every change contains multiple items which have been modified. The Items method unwraps these objects and returns a list instead.

Note that if a item was changed multiple times it will be present multiple times in the result too.

Example
client := New(APIEndpoint, os.Getenv("THINGS_USERNAME"), os.Getenv("THINGS_PASSWORD"))

histories, err := client.Histories()
if err != nil {
	log.Printf("Failed loading histories: %q", err.Error())
}
history := histories[0]

items, _, err := history.Items(ItemsOptions{})
if err != nil {
	log.Printf("Failed loading items: %q", err.Error())
}

log.Printf("got %d items.", len(items))

func (*History) Sync

func (h *History) Sync() error

Sync ensures the history object is able to write to things

func (*History) Write

func (h *History) Write(items ...Identifiable) error

type Identifiable

type Identifiable interface {
	UUID() string
}

Identifiable abstracts different thingscloud write requests. As we need to provide a map indexed by UUID, all we care about is the ID of the change, not the change itself

type Item

type Item struct {
	UUID           string          `json:"-"`
	P              json.RawMessage `json:"p"`
	Kind           ItemKind        `json:"e"`
	Action         ItemAction      `json:"t"`
	ServerIndex    int             `json:"-"`
	HasServerIndex bool            `json:"-"`
}

Item is an event in thingscloud. Every action inside things generates an Item. Common items are the creation of a task, area or checklist, as well as modifying attributes or marking things as done.

type ItemAction

type ItemAction int

ItemAction describes possible actions on Items

const (
	// ItemActionCreated is used to indicate a new Item was created
	ItemActionCreated ItemAction = iota
	// ItemActionModified is used to indicate an existing Item was modified
	ItemActionModified ItemAction = 1
	// ItemActionDeleted is used as a tombstone for an Item
	ItemActionDeleted ItemAction = 2
)

func (ItemAction) String

func (i ItemAction) String() string

type ItemKind

type ItemKind string

ItemKind describes the different types things cloud supports

var (
	// ItemKindChecklistItem identifies a CheckList
	ItemKindChecklistItem  ItemKind = "ChecklistItem"
	ItemKindChecklistItem2 ItemKind = "ChecklistItem2"
	ItemKindChecklistItem3 ItemKind = "ChecklistItem3"
	// ItemKindTask identifies a Task or Subtask
	ItemKindTask      ItemKind = "Task6"
	ItemKindTask4     ItemKind = "Task4"
	ItemKindTask3     ItemKind = "Task3"
	ItemKindTaskPlain ItemKind = "Task"
	// ItemKindArea identifies an Area
	ItemKindArea      ItemKind = "Area2"
	ItemKindArea3     ItemKind = "Area3"
	ItemKindAreaPlain ItemKind = "Area"
	// ItemKindSettings  identifies a setting
	ItemKindSettings ItemKind = "Settings3"
	// ItemKindTag identifies a Tag
	ItemKindTag       ItemKind = "Tag3"
	ItemKindTag4      ItemKind = "Tag4"
	ItemKindTagPlain  ItemKind = "Tag"
	ItemKindTombstone ItemKind = "Tombstone2"
)

type ItemsOptions

type ItemsOptions struct {
	StartIndex int
}

ItemsOptions allows a client to pickup changes from a specific index

type Note added in v0.2.1

type Note struct {
	TypeTag  string      `json:"_t"`
	Type     int         `json:"t"`
	Checksum int64       `json:"ch,omitempty"`
	Value    string      `json:"v,omitempty"`
	Patches  []NotePatch `json:"ps,omitempty"`
}

Note describes a structured note as used by the Things API

type NotePatch added in v0.2.1

type NotePatch struct {
	Replacement string `json:"r"`
	Position    int    `json:"p"`
	Length      int    `json:"l"`
	Checksum    int64  `json:"ch"`
}

NotePatch describes a single text replacement operation

type RepeaterConfiguration added in v0.1.1

type RepeaterConfiguration struct {
	FirstScheduledAt    *Timestamp                    `json:"ia,omitempty"`
	RepeatCount         *int64                        `json:"rc,omitempty"`
	FrequencyUnit       FrequencyUnit                 `json:"fu"`
	FrequencyAmplitude  int64                         `json:"fa"`
	DetailConfiguration []RepeaterDetailConfiguration `json:"of"`
	LastScheduledAt     *Timestamp                    `json:"ed,omitempty"`
	Version             int                           `json:"rrv"`
	Type                int                           `json:"tp"`
	TimeShift           int                           `json:"ts"`
	StartReference      *Timestamp                    `json:"sr,omitempty"`
}

RepeaterConfiguration configures the recurring rules of a task/ project

func NewRepeatAfterCompletion added in v0.2.1

func NewRepeatAfterCompletion(unit FrequencyUnit, amplitude int64, start time.Time) RepeaterConfiguration

NewRepeatAfterCompletion creates a basic repeat-after-completion rule. Callers can customize DetailConfiguration and end conditions before writing.

func (RepeaterConfiguration) ComputeFirstScheduledAt added in v0.1.1

func (c RepeaterConfiguration) ComputeFirstScheduledAt(t time.Time) time.Time

ComputeFirstScheduledAt calculates the first occurrence of a recurring rule based on the pattern This value has to be stored as FirstScheduledAt per thingscloud convention

func (RepeaterConfiguration) IsAfterCompletion added in v0.2.1

func (c RepeaterConfiguration) IsAfterCompletion() bool

IsAfterCompletion reports whether the rule repeats after completion.

func (RepeaterConfiguration) IsNeverending added in v0.1.1

func (c RepeaterConfiguration) IsNeverending() bool

IsNeverending determines if a recurring rule has a specific end

func (RepeaterConfiguration) NextScheduledAt added in v0.1.1

func (c RepeaterConfiguration) NextScheduledAt(repeat int) time.Time

NextScheduledAt returns the next Nth date a rule should occur. Note that things generates these ToDos as necessary.

type RepeaterDetailConfiguration added in v0.1.1

type RepeaterDetailConfiguration struct {
	Day     *int64        `json:"dy,omitempty"`
	Month   *int64        `json:"mo,omitempty"`
	Weekday *time.Weekday `json:"wd,omitempty"`
	MonthOf *int64        `json:"wdo,omitempty"`
}

RepeaterDetailConfiguration configures specifics of a repeater configuration.

type RepeaterType added in v0.2.1

type RepeaterType int

RepeaterType describes whether a repeating task is scheduled from its scheduled date or after the previous completion.

const (
	// RepeaterTypeScheduled repeats from the scheduled date.
	RepeaterTypeScheduled RepeaterType = 0
	// RepeaterTypeAfterCompletion repeats after the task is completed.
	RepeaterTypeAfterCompletion RepeaterType = 1
)

type Setting

type Setting struct{}

Setting describes things settings 0|uuid|TEXT|0||1 1|logInterval|INTEGER|0||0 2|manualLogDate|REAL|0||0 3|groupTodayByParent|INTEGER|0||0

type Tag

type Tag struct {
	UUID         string
	Title        string
	ParentTagIDs []string
	ShortHand    string
}

Tag describes the aggregated state of an Tag 0|uuid|TEXT|0||1 1|title|TEXT|0||0 2|shortcut|TEXT|0||0 3|usedDate|REAL|0||0 4|parent|TEXT|0||0 5|index|INTEGER|0||0

type TagActionItem

type TagActionItem struct {
	Item
	P TagActionItemPayload `json:"p"`
}

TagActionItem describes an event on a tag

func (TagActionItem) UUID

func (t TagActionItem) UUID() string

UUID returns the UUID of the modified Tag

type TagActionItemPayload

type TagActionItemPayload struct {
	IX            *int            `json:"ix"`
	Title         *string         `json:"tt"`
	ShortHand     *string         `json:"sh"`
	ParentTagIDs  *[]string       `json:"pn"`
	ExtensionData json.RawMessage `json:"xx,omitempty"`
}

TagActionItemPayload describes the payload for modifying Areas

type Task

type Task struct {
	UUID             string
	CreationDate     time.Time
	ModificationDate *time.Time
	Status           TaskStatus
	Title            string
	Note             string
	ScheduledDate    *time.Time
	CompletionDate   *time.Time
	DeadlineDate     *time.Time
	Index            int
	AreaIDs          []string
	ParentTaskIDs    []string
	ActionGroupIDs   []string
	InTrash          bool
	Schedule         TaskSchedule
	Type             TaskType
	TodayIndex       int
	DueOrder         int
	AlarmTimeOffset  *int
	TagIDs           []string
	RecurrenceIDs    []string
	DelegateIDs      []string
}

Task describes a Task inside things. 0|uuid|TEXT|0||1 1|userModificationDate|REAL|0||0 2|creationDate|REAL|0||0 3|trashed|INTEGER|0||0 4|type|INTEGER|0||0 5|title|TEXT|0||0 6|notes|TEXT|0||0 7|dueDate|REAL|0||0 8|dueDateOffset|INTEGER|0||0 9|status|INTEGER|0||0 10|stopDate|REAL|0||0 11|start|INTEGER|0||0 12|startDate|REAL|0||0 13|index|INTEGER|0||0 14|todayIndex|INTEGER|0||0 15|area|TEXT|0||0 16|project|TEXT|0||0 17|repeatingTemplate|TEXT|0||0 18|delegate|TEXT|0||0 19|recurrenceRule|BLOB|0||0 20|instanceCreationStartDate|REAL|0||0 21|instanceCreationPaused|INTEGER|0||0 22|instanceCreationCount|INTEGER|0||0 23|afterCompletionReferenceDate|REAL|0||0 24|actionGroup|TEXT|0||0 25|untrashedLeafActionsCount|INTEGER|0||0 26|openUntrashedLeafActionsCount|INTEGER|0||0 27|checklistItemsCount|INTEGER|0||0 28|openChecklistItemsCount|INTEGER|0||0 29|startBucket|INTEGER|0||0 30|alarmTimeOffset|REAL|0||0 31|lastAlarmInteractionDate|REAL|0||0 32|todayIndexReferenceDate|REAL|0||0 33|nextInstanceStartDate|REAL|0||0 34|dueDateSuppressionDate|REAL|0||0

type TaskActionItem

type TaskActionItem struct {
	Item
	P TaskActionItemPayload `json:"p"`
}

TaskActionItem describes an event on a Task

func (TaskActionItem) UUID

func (t TaskActionItem) UUID() string

UUID returns the UUID of the modified Task

type TaskActionItemPayload

type TaskActionItemPayload struct {
	Index                     *int                   `json:"ix,omitempty"`
	CreationDate              *Timestamp             `json:"cd,omitempty"`
	ModificationDate          *Timestamp             `json:"md,omitempty"` // ok
	ScheduledDate             *Timestamp             `json:"sr,omitempty"`
	CompletionDate            *Timestamp             `json:"sp,omitempty"`
	DeadlineDate              *Timestamp             `json:"dd,omitempty"`  //
	TaskIR                    *Timestamp             `json:"tir,omitempty"` // hm, not sure what tir stands for
	Status                    *TaskStatus            `json:"ss,omitempty"`
	Type                      *TaskType              `json:"tp,omitempty"`
	Title                     *string                `json:"tt,omitempty"`
	Note                      json.RawMessage        `json:"nt,omitempty"`
	AreaIDs                   *[]string              `json:"ar,omitempty"`
	ParentTaskIDs             *[]string              `json:"pr,omitempty"`
	TagIDs                    []string               `json:"tg,omitempty"`
	InTrash                   *bool                  `json:"tr,omitempty"`
	TaskIndex                 *int                   `json:"ti,omitempty"`
	RecurrenceTaskIDs         *[]string              `json:"rt,omitempty"`
	Schedule                  *TaskSchedule          `json:"st,omitempty"`
	ActionGroupIDs            *[]string              `json:"agr,omitempty"`
	Repeater                  *RepeaterConfiguration `json:"rr,omitempty"`
	DueOrder                  *int                   `json:"do,omitempty"`
	Leavable                  *bool                  `json:"lt,omitempty"`
	IsCompletedByChildren     *bool                  `json:"icp,omitempty"`
	IsCompletedCount          *int                   `json:"icc,omitempty"`
	InstanceCreationStartDate *Timestamp             `json:"icsd,omitempty"`
	SubtaskBehavior           *int                   `json:"sb,omitempty"`
	DelegateIDs               *[]string              `json:"dl,omitempty"`
	LastActionItemID          *Timestamp             `json:"lai,omitempty"`
	ReminderDate              *Timestamp             `json:"rmd,omitempty"`
	AlarmTimeOffset           *int                   `json:"ato,omitempty"`
	ActionRequiredDate        *Timestamp             `json:"acrd,omitempty"`
	DeadlineSuppression       *Timestamp             `json:"dds,omitempty"`
	ExtensionData             json.RawMessage        `json:"xx,omitempty"`
}

TaskActionItemPayload describes the payload for modifying Tasks, and also Projects, as projects are special kind of Tasks

type TaskSchedule

type TaskSchedule int

TaskSchedule describes when a task is scheduled

const (
	// TaskScheduleInbox indicates unprocessed tasks in the Inbox (st=0)
	TaskScheduleInbox TaskSchedule = 0
	// TaskScheduleAnytime indicates started tasks; displayed in Today when sr/tir
	// are set to today's date, or in Anytime when sr/tir are null (st=1)
	TaskScheduleAnytime TaskSchedule = 1
	// TaskScheduleSomeday indicates deferred tasks; displayed in Upcoming when
	// sr/tir have a future date, or in Someday when sr/tir are null (st=2)
	TaskScheduleSomeday TaskSchedule = 2

	// TaskScheduleToday is deprecated: use TaskScheduleAnytime with sr/tir dates.
	// The "st" field value 0 actually means Inbox, not Today.
	// Kept for backward compatibility.
	TaskScheduleToday = TaskScheduleInbox
)

func Schedule added in v0.2.1

func Schedule(val TaskSchedule) *TaskSchedule

Schedule returns a pointer to a TaskSchedule

func (TaskSchedule) String

func (i TaskSchedule) String() string

type TaskStatus

type TaskStatus int

TaskStatus describes if a thing is completed or not

const (
	// TaskStatusPending indicates a new task
	TaskStatusPending TaskStatus = iota
	// TaskStatusCompleted indicates a completed task
	TaskStatusCompleted TaskStatus = 3
	// TaskStatusCanceled indicates a canceled task
	TaskStatusCanceled TaskStatus = 2
)

func Status added in v0.2.1

func Status(val TaskStatus) *TaskStatus

Status returns a pointer to a TaskStatus

func (TaskStatus) String

func (i TaskStatus) String() string

type TaskType added in v0.2.1

type TaskType int

TaskType describes the type of a task entity

const (
	TaskTypeTask    TaskType = 0
	TaskTypeProject TaskType = 1
	TaskTypeHeading TaskType = 2
)

func TaskTypePtr added in v0.2.1

func TaskTypePtr(val TaskType) *TaskType

TaskTypePtr returns a pointer to a TaskType

type Timestamp

type Timestamp time.Time

Timestamp allows unix epochs represented as float or ints to be unmarshalled into time.Time objects

func Time added in v0.2.1

func Time(val time.Time) *Timestamp

Time returns a pointer to a Time

func (*Timestamp) Format

func (t *Timestamp) Format(layout string) string

Format returns a textual representation of the time value formatted according to layout

func (*Timestamp) MarshalJSON

func (t *Timestamp) MarshalJSON() ([]byte, error)

MarshalJSON converts a timestamp into a fractional unix epoch (seconds with sub-second precision), matching the format used by the Things Cloud API (e.g. 1770713623.4716659).

func (*Timestamp) Time

func (t *Timestamp) Time() *time.Time

Time returns the underlying time.Time instance

func (*Timestamp) UnmarshalJSON

func (t *Timestamp) UnmarshalJSON(bs []byte) error

UnmarshalJSON takes a unix epoch from float/ int and creates a time.Time instance, preserving sub-second precision.

type TombstoneActionItem added in v0.2.1

type TombstoneActionItem struct {
	Item
	P TombstoneActionItemPayload `json:"p"`
}

TombstoneActionItem describes a tombstone deletion event

func (TombstoneActionItem) UUID added in v0.2.1

func (t TombstoneActionItem) UUID() string

UUID returns the UUID of the TombstoneActionItem

type TombstoneActionItemPayload added in v0.2.1

type TombstoneActionItemPayload struct {
	DeletedObjectID string  `json:"dloid"`
	DeletionDate    float64 `json:"dld"`
}

TombstoneActionItemPayload describes the payload for tombstone deletion records

type VerifyResponse

type VerifyResponse struct {
	SLAVersionAccepted string          `json:"SLA-version-accepted"`
	Issues             json.RawMessage `json:"issues"`
	Email              string          `json:"email"`
	MaildropEmail      string          `json:"maildrop-email"`
	Status             AccountStatus   `json:"status"`
	HistoryKey         string          `json:"history-key"`
}

VerifyResponse contains details about your account

Directories

Path Synopsis
cmd
debug command
debugupdate command
findtask command
fullstate command
list command
rawitem command
rawtask command
recent command
statedebug command
synctest command
things-cli command
things-mcp command
thingsync command
trace command
internal
Package local provides a read-only adapter for local Things SQLite databases.
Package local provides a read-only adapter for local Things SQLite databases.
state
Package sync provides a persistent sync engine for Things Cloud.
Package sync provides a persistent sync engine for Things Cloud.
Package syncutil provides shared utilities for sync-based CLI tools.
Package syncutil provides shared utilities for sync-based CLI tools.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL