things3

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Nov 30, 2025 License: Apache-2.0 Imports: 13 Imported by: 0

README

things3

Go CI codecov Go Reference

Go library for read-only access to the Things 3 macOS app database. A Go port of things.py with full API parity.

Installation

go get github.com/moond4rk/things3

Note: Requires CGO enabled (uses go-sqlite3). macOS only.

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/moond4rk/things3"
)

func main() {
    client, err := things3.New()
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    ctx := context.Background()

    // Get inbox tasks
    inbox, _ := client.Inbox(ctx)
    fmt.Printf("Inbox: %d tasks\n", len(inbox))

    // Get today's tasks
    today, _ := client.Today(ctx)
    for _, task := range today {
        fmt.Printf("- %s\n", task.Title)
    }
}

Core API

Convenience Methods
client.Inbox(ctx)      // Tasks in Inbox
client.Today(ctx)      // Today's tasks
client.Upcoming(ctx)   // Scheduled future tasks
client.Anytime(ctx)    // Anytime tasks
client.Someday(ctx)    // Someday tasks
client.Logbook(ctx)    // Completed/canceled tasks
client.Trash(ctx)      // Trashed tasks
client.Todos(ctx)      // All incomplete to-dos
client.Projects(ctx)   // All incomplete projects
client.Deadlines(ctx)  // Tasks with deadlines
client.Search(ctx, "query")     // Search tasks
client.Last(ctx, "7d")          // Tasks from last 7 days
Query Builder

For complex queries, use the fluent query builder:

// Find incomplete to-dos in a specific project
tasks, err := client.Tasks().
    WithType(things3.TaskTypeTodo).
    WithStatus(things3.StatusIncomplete).
    InProject("project-uuid").
    All(ctx)

// Find tasks with a specific tag
tasks, err := client.Tasks().
    WithTag("work").
    All(ctx)

// Get a single task by UUID
task, err := client.Tasks().
    WithUUID("task-uuid").
    First(ctx)

// Count matching tasks
count, err := client.Tasks().
    WithStatus(things3.StatusCompleted).
    Count(ctx)
Areas and Tags
// Get all areas
areas, err := client.Areas().All(ctx)

// Get area with its tasks
areas, err := client.Areas().
    IncludeItems(true).
    All(ctx)

// Get all tags
tags, err := client.Tags().All(ctx)

Configuration

// Use custom database path
client, err := things3.New(
    things3.WithDatabasePath("/path/to/main.sqlite"),
)

// Enable SQL logging for debugging
client, err := things3.New(
    things3.WithPrintSQL(true),
)
Database Discovery

The database path is resolved in order:

  1. Custom path via WithDatabasePath()
  2. THINGSDB environment variable
  3. Default Things 3 location: ~/Library/Group Containers/JLMPQHK86H.com.culturedcode.ThingsMac/Things Database.thingsdatabase/main.sqlite

Types

// Task types
things3.TaskTypeTodo     // 0 - To-do item
things3.TaskTypeProject  // 1 - Project
things3.TaskTypeHeading  // 2 - Heading

// Status
things3.StatusIncomplete // 0
things3.StatusCanceled   // 2
things3.StatusCompleted  // 3

// Start bucket
things3.StartInbox    // 0
things3.StartAnytime  // 1
things3.StartSomeday  // 2

License

Apache License 2.0

Documentation

Overview

Package things3 provides read-only access to the Things 3 macOS application's SQLite database.

This package is a Go port of the Python things.py library, offering full API parity for querying tasks, projects, areas, and tags from the Things 3 app.

Basic Usage

Create a client and query tasks:

client, err := things3.New()
if err != nil {
    log.Fatal(err)
}
defer client.Close()

// Get inbox tasks
inbox, err := client.Inbox(ctx)

// Get all incomplete to-dos
todos, err := client.Todos(ctx)

Query Builder

For complex queries, use the fluent query builder:

tasks, err := client.Tasks().
    WithType(things3.TaskTypeTodo).
    WithStatus(things3.StatusIncomplete).
    InProject("project-uuid").
    WithDeadline(things3.DateOpExists).
    All(ctx)

Configuration

Configure the client with functional options:

// Use custom database path
client, err := things3.New(things3.WithDatabasePath("/path/to/main.sqlite"))

// Enable SQL query logging
client, err := things3.New(things3.WithPrintSQL(true))

Database Discovery

The database path is discovered in the following order:

  1. Custom path provided via WithDatabasePath option
  2. THINGSDB environment variable
  3. Auto-discovery of default Things 3 database location

Type System

The package uses integer-based enums that map directly to database values:

  • TaskType: TaskTypeTodo (0), TaskTypeProject (1), TaskTypeHeading (2)
  • Status: StatusIncomplete (0), StatusCanceled (2), StatusCompleted (3)
  • StartBucket: StartInbox (0), StartAnytime (1), StartSomeday (2)

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrDatabaseNotFound is returned when the Things database cannot be located.
	ErrDatabaseNotFound = errors.New("things3: database not found")

	// ErrTaskNotFound is returned when a task with the specified UUID does not exist.
	ErrTaskNotFound = errors.New("things3: task not found")

	// ErrAreaNotFound is returned when an area with the specified UUID does not exist.
	ErrAreaNotFound = errors.New("things3: area not found")

	// ErrTagNotFound is returned when a tag with the specified title does not exist.
	ErrTagNotFound = errors.New("things3: tag not found")

	// ErrInvalidParameter is returned when an invalid parameter value is provided.
	ErrInvalidParameter = errors.New("things3: invalid parameter")

	// ErrDatabaseVersionTooOld is returned when the database version is not supported.
	ErrDatabaseVersionTooOld = errors.New("things3: database version too old (requires version > 21)")

	// ErrAuthTokenNotFound is returned when the URL scheme auth token cannot be read.
	ErrAuthTokenNotFound = errors.New("things3: auth token not found")
)

Functions

This section is empty.

Types

type Area

type Area struct {
	UUID  string `json:"uuid"`
	Type  string `json:"type"` // Always "area"
	Title string `json:"title"`

	// Nested items (populated when include_items=true)
	Tags  []string `json:"tags,omitempty"`
	Items []Task   `json:"items,omitempty"`
}

Area represents an area in Things 3.

type AreaQuery

type AreaQuery struct {
	// contains filtered or unexported fields
}

AreaQuery provides a fluent interface for building area queries.

func (*AreaQuery) All

func (q *AreaQuery) All(ctx context.Context) ([]Area, error)

All executes the query and returns all matching areas.

func (*AreaQuery) Count

func (q *AreaQuery) Count(ctx context.Context) (int, error)

Count executes the query and returns the count of matching areas.

func (*AreaQuery) First

func (q *AreaQuery) First(ctx context.Context) (*Area, error)

First executes the query and returns the first matching area.

func (*AreaQuery) IncludeItems

func (q *AreaQuery) IncludeItems(include bool) *AreaQuery

IncludeItems includes tasks in each area.

func (*AreaQuery) WithTag

func (q *AreaQuery) WithTag(tag any) *AreaQuery

WithTag filters areas by tag.

func (*AreaQuery) WithUUID

func (q *AreaQuery) WithUUID(uuid string) *AreaQuery

WithUUID filters areas by UUID.

type ChecklistItem

type ChecklistItem struct {
	UUID     string     `json:"uuid"`
	Type     string     `json:"type"` // Always "checklist-item"
	Title    string     `json:"title"`
	Status   string     `json:"status"` // "incomplete", "completed", or "canceled"
	StopDate *time.Time `json:"stop_date,omitempty"`
	Created  time.Time  `json:"created"`
	Modified time.Time  `json:"modified"`
}

ChecklistItem represents a checklist item within a to-do.

func (*ChecklistItem) IsCanceled

func (c *ChecklistItem) IsCanceled() bool

IsCanceled returns true if the checklist item is canceled.

func (*ChecklistItem) IsCompleted

func (c *ChecklistItem) IsCompleted() bool

IsCompleted returns true if the checklist item is completed.

func (*ChecklistItem) IsIncomplete

func (c *ChecklistItem) IsIncomplete() bool

IsIncomplete returns true if the checklist item is incomplete.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client provides read-only access to the Things 3 database.

func New

func New(opts ...Option) (*Client, error)

New creates a new Things 3 client. Options can be provided to configure the client behavior.

func (*Client) AddProjectURL

func (c *Client) AddProjectURL(params map[string]string) string

AddProjectURL returns a URL to add a new project with the given parameters.

func (*Client) AddTodoURL

func (c *Client) AddTodoURL(params map[string]string) string

AddTodoURL returns a URL to add a new to-do with the given parameters.

func (*Client) Anytime

func (c *Client) Anytime(ctx context.Context) ([]Task, error)

Anytime returns tasks in the Anytime list.

func (*Client) Areas

func (c *Client) Areas() *AreaQuery

Areas creates a new AreaQuery for querying areas.

func (*Client) Canceled

func (c *Client) Canceled(ctx context.Context) ([]Task, error)

Canceled returns canceled tasks.

func (*Client) ChecklistItems

func (c *Client) ChecklistItems(ctx context.Context, todoUUID string) ([]ChecklistItem, error)

ChecklistItems returns the checklist items for a to-do.

func (*Client) Close

func (c *Client) Close() error

Close closes the database connection.

func (*Client) Complete

func (c *Client) Complete(ctx context.Context, uuid string) error

Complete marks a task as complete using the Things URL scheme. Requires the URL scheme authentication token to be set in Things.

func (*Client) Completed

func (c *Client) Completed(ctx context.Context) ([]Task, error)

Completed returns completed tasks.

func (*Client) Deadlines

func (c *Client) Deadlines(ctx context.Context) ([]Task, error)

Deadlines returns tasks with deadlines, sorted by deadline.

func (*Client) Filepath

func (c *Client) Filepath() string

Filepath returns the path to the Things database file.

func (*Client) Get

func (c *Client) Get(ctx context.Context, uuid string) (any, error)

Get retrieves an object by UUID. Returns a Task, Area, or Tag depending on what is found. Returns nil if not found.

func (*Client) Inbox

func (c *Client) Inbox(ctx context.Context) ([]Task, error)

Inbox returns all tasks in the Inbox.

func (*Client) Last

func (c *Client) Last(ctx context.Context, offset string) ([]Task, error)

Last returns tasks created within the last X days/weeks/years. Format: "3d" (3 days), "2w" (2 weeks), "1y" (1 year).

func (c *Client) Link(uuid string) string

Link returns a things:// URL that shows the item. Alias for ShowURL for backwards compatibility.

func (*Client) Logbook

func (c *Client) Logbook(ctx context.Context) ([]Task, error)

Logbook returns completed and canceled tasks, sorted by stop date.

func (*Client) OpenSearch

func (c *Client) OpenSearch(ctx context.Context, query string) error

OpenSearch opens Things and performs a search for the given query.

func (*Client) Projects

func (c *Client) Projects(ctx context.Context) ([]Task, error)

Projects returns all incomplete projects.

func (*Client) Search

func (c *Client) Search(ctx context.Context, query string) ([]Task, error)

Search searches for tasks matching the query. Searches in task title, notes, and area title.

func (*Client) SearchURL

func (c *Client) SearchURL(query string) string

SearchURL returns a URL to search for the given query in Things.

func (*Client) Show

func (c *Client) Show(ctx context.Context, uuid string) error

Show opens Things and shows the item with the given UUID.

func (*Client) ShowURL

func (c *Client) ShowURL(uuid string) string

ShowURL returns a URL to show an item in Things.

func (*Client) Someday

func (c *Client) Someday(ctx context.Context) ([]Task, error)

Someday returns tasks in the Someday list (without a start date).

func (*Client) Tags

func (c *Client) Tags() *TagQuery

Tags creates a new TagQuery for querying tags.

func (*Client) Tasks

func (c *Client) Tasks() *TaskQuery

Tasks creates a new TaskQuery for querying tasks.

func (*Client) Today

func (c *Client) Today(ctx context.Context) ([]Task, error)

Today returns tasks that would appear in Today view. This includes: - Tasks with a start date set to today or earlier and in Anytime - Scheduled tasks from Someday with past start dates (yellow dot tasks) - Overdue tasks with deadlines that haven't been suppressed

func (*Client) Todos

func (c *Client) Todos(ctx context.Context) ([]Task, error)

Todos returns all incomplete to-do items.

func (*Client) Token

func (c *Client) Token(ctx context.Context) (string, error)

Token returns the Things URL scheme authentication token.

func (*Client) Trash

func (c *Client) Trash(ctx context.Context) ([]Task, error)

Trash returns trashed tasks.

func (*Client) URL

func (c *Client) URL(ctx context.Context, cmd URLCommand, params map[string]string) (string, error)

URL builds a Things URL scheme URL. See https://culturedcode.com/things/help/url-scheme/ for details.

func (*Client) Upcoming

func (c *Client) Upcoming(ctx context.Context) ([]Task, error)

Upcoming returns tasks scheduled for future dates.

type Option

type Option func(*clientOptions)

Option is a functional option for configuring the Client.

func WithDatabasePath

func WithDatabasePath(path string) Option

WithDatabasePath sets a custom path to the Things database. If not set, the database path is discovered automatically.

func WithPrintSQL

func WithPrintSQL(enabled bool) Option

WithPrintSQL enables SQL query logging to stdout. Useful for debugging and understanding the queries being executed.

type StartBucket

type StartBucket int

StartBucket represents the scheduling bucket for a task.

const (
	// StartInbox indicates the task is in the Inbox.
	StartInbox StartBucket = 0
	// StartAnytime indicates the task is scheduled for Anytime.
	StartAnytime StartBucket = 1
	// StartSomeday indicates the task is scheduled for Someday.
	StartSomeday StartBucket = 2
)

func (StartBucket) String

func (s StartBucket) String() string

String returns the string representation of the StartBucket.

type Status

type Status int

Status represents the completion status of a task.

const (
	// StatusIncomplete indicates the task is not yet completed.
	StatusIncomplete Status = 0
	// StatusCanceled indicates the task was canceled.
	StatusCanceled Status = 2
	// StatusCompleted indicates the task was completed.
	StatusCompleted Status = 3
)

func (Status) IsClosed

func (s Status) IsClosed() bool

IsClosed returns true if the status indicates a closed (completed or canceled) task.

func (Status) IsOpen

func (s Status) IsOpen() bool

IsOpen returns true if the status indicates an open (incomplete) task.

func (Status) String

func (s Status) String() string

String returns the string representation of the Status.

type Tag

type Tag struct {
	UUID     string `json:"uuid"`
	Type     string `json:"type"` // Always "tag"
	Title    string `json:"title"`
	Shortcut string `json:"shortcut,omitempty"`

	// Nested items (populated when include_items=true)
	Items []any `json:"items,omitempty"` // Can contain Area or Task
}

Tag represents a tag in Things 3.

type TagQuery

type TagQuery struct {
	// contains filtered or unexported fields
}

TagQuery provides a fluent interface for building tag queries.

func (*TagQuery) All

func (q *TagQuery) All(ctx context.Context) ([]Tag, error)

All executes the query and returns all matching tags.

func (*TagQuery) First

func (q *TagQuery) First(ctx context.Context) (*Tag, error)

First executes the query and returns the first matching tag.

func (*TagQuery) IncludeItems

func (q *TagQuery) IncludeItems(include bool) *TagQuery

IncludeItems includes areas and tasks for each tag.

func (*TagQuery) WithTitle

func (q *TagQuery) WithTitle(title string) *TagQuery

WithTitle filters tags by title.

type Task

type Task struct {
	UUID   string   `json:"uuid"`
	Type   TaskType `json:"type"`
	Title  string   `json:"title"`
	Status Status   `json:"status"`
	Notes  string   `json:"notes,omitempty"`
	Start  string   `json:"start,omitempty"` // "Inbox", "Anytime", or "Someday"

	// Trashed indicates whether the task is in the trash.
	Trashed bool `json:"trashed,omitempty"`

	// Relationships
	AreaUUID     *string `json:"area,omitempty"`
	AreaTitle    *string `json:"area_title,omitempty"`
	ProjectUUID  *string `json:"project,omitempty"`
	ProjectTitle *string `json:"project_title,omitempty"`
	HeadingUUID  *string `json:"heading,omitempty"`
	HeadingTitle *string `json:"heading_title,omitempty"`

	// Dates
	StartDate    *string    `json:"start_date,omitempty"`    // ISO 8601 date
	Deadline     *string    `json:"deadline,omitempty"`      // ISO 8601 date
	ReminderTime *string    `json:"reminder_time,omitempty"` // HH:MM format
	StopDate     *time.Time `json:"stop_date,omitempty"`     // Completion/cancellation date
	Created      time.Time  `json:"created"`
	Modified     time.Time  `json:"modified"`

	// Index values for ordering
	Index      int `json:"index"`
	TodayIndex int `json:"today_index"`

	// Nested items (populated when include_items=true)
	Tags      []string        `json:"tags,omitempty"`
	Checklist []ChecklistItem `json:"checklist,omitempty"`
	Items     []Task          `json:"items,omitempty"` // For projects and headings
}

Task represents a task in Things 3, which can be a to-do, project, or heading.

func (*Task) HasChecklist

func (t *Task) HasChecklist() bool

HasChecklist returns true if the task has a checklist.

func (*Task) HasTags

func (t *Task) HasTags() bool

HasTags returns true if the task has any tags.

func (*Task) IsCanceled

func (t *Task) IsCanceled() bool

IsCanceled returns true if the task status is canceled.

func (*Task) IsCompleted

func (t *Task) IsCompleted() bool

IsCompleted returns true if the task status is completed.

func (*Task) IsHeading

func (t *Task) IsHeading() bool

IsHeading returns true if the task is a heading.

func (*Task) IsIncomplete

func (t *Task) IsIncomplete() bool

IsIncomplete returns true if the task status is incomplete.

func (*Task) IsProject

func (t *Task) IsProject() bool

IsProject returns true if the task is a project.

func (*Task) IsTodo

func (t *Task) IsTodo() bool

IsTodo returns true if the task is a to-do item.

type TaskQuery

type TaskQuery struct {
	// contains filtered or unexported fields
}

TaskQuery provides a fluent interface for building task queries.

func (*TaskQuery) All

func (q *TaskQuery) All(ctx context.Context) ([]Task, error)

All executes the query and returns all matching tasks.

func (*TaskQuery) ContextTrashed

func (q *TaskQuery) ContextTrashed(trashed bool) *TaskQuery

ContextTrashed filters tasks by the trash status of their context (project/heading).

func (*TaskQuery) Count

func (q *TaskQuery) Count(ctx context.Context) (int, error)

Count executes the query and returns the count of matching tasks.

func (*TaskQuery) First

func (q *TaskQuery) First(ctx context.Context) (*Task, error)

First executes the query and returns the first matching task.

func (*TaskQuery) InArea

func (q *TaskQuery) InArea(area any) *TaskQuery

InArea filters tasks by area. Pass a UUID string to filter by specific area. Pass true to include only tasks with an area. Pass false to include only tasks without an area.

func (*TaskQuery) InHeading

func (q *TaskQuery) InHeading(heading any) *TaskQuery

InHeading filters tasks by heading. Pass a UUID string to filter by specific heading. Pass true to include only tasks with a heading. Pass false to include only tasks without a heading.

func (*TaskQuery) InProject

func (q *TaskQuery) InProject(project any) *TaskQuery

InProject filters tasks by project. Pass a UUID string to filter by specific project. Pass true to include only tasks with a project. Pass false to include only tasks without a project.

func (*TaskQuery) IncludeItems

func (q *TaskQuery) IncludeItems(include bool) *TaskQuery

IncludeItems includes nested items (checklist for to-dos, tasks for projects/headings).

func (*TaskQuery) Last

func (q *TaskQuery) Last(offset string) *TaskQuery

Last filters tasks created within the last X days/weeks/years. Format: "3d" (3 days), "2w" (2 weeks), "1y" (1 year).

func (*TaskQuery) OrderByTodayIndex

func (q *TaskQuery) OrderByTodayIndex() *TaskQuery

OrderByTodayIndex orders results by today index instead of default index.

func (*TaskQuery) Search

func (q *TaskQuery) Search(query string) *TaskQuery

Search filters tasks by a search query. Searches in task title, notes, and area title.

func (*TaskQuery) Trashed

func (q *TaskQuery) Trashed(trashed bool) *TaskQuery

Trashed filters tasks by trash status. Pass true to include only trashed tasks. Pass false to include only non-trashed tasks.

func (*TaskQuery) WithDeadline

func (q *TaskQuery) WithDeadline(deadline any) *TaskQuery

WithDeadline filters tasks by deadline. Accepts: bool (has/doesn't have), "future", "past", or ISO date with optional operator.

func (*TaskQuery) WithDeadlineSuppressed

func (q *TaskQuery) WithDeadlineSuppressed(suppressed bool) *TaskQuery

WithDeadlineSuppressed filters tasks by deadline suppression status.

func (*TaskQuery) WithStart

func (q *TaskQuery) WithStart(s StartBucket) *TaskQuery

WithStart filters tasks by start bucket (Inbox, Anytime, Someday).

func (*TaskQuery) WithStartDate

func (q *TaskQuery) WithStartDate(date any) *TaskQuery

WithStartDate filters tasks by start date. Accepts: bool (has/doesn't have), "future", "past", or ISO date with optional operator.

func (*TaskQuery) WithStatus

func (q *TaskQuery) WithStatus(s Status) *TaskQuery

WithStatus filters tasks by status.

func (*TaskQuery) WithStopDate

func (q *TaskQuery) WithStopDate(date any) *TaskQuery

WithStopDate filters tasks by stop date (completion/cancellation date). Accepts: bool (has/doesn't have), "future", "past", or ISO date with optional operator.

func (*TaskQuery) WithTag

func (q *TaskQuery) WithTag(tag any) *TaskQuery

WithTag filters tasks by tag. Pass a tag title to filter by specific tag. Pass true to include only tasks with tags. Pass false to include only tasks without tags.

func (*TaskQuery) WithType

func (q *TaskQuery) WithType(t TaskType) *TaskQuery

WithType filters tasks by type (to-do, project, or heading).

func (*TaskQuery) WithUUID

func (q *TaskQuery) WithUUID(uuid string) *TaskQuery

WithUUID filters tasks by UUID.

type TaskType

type TaskType int

TaskType represents the type of a task in Things 3. Tasks can be to-dos, projects, or headings within projects.

const (
	// TaskTypeTodo represents a regular to-do item.
	TaskTypeTodo TaskType = 0
	// TaskTypeProject represents a project containing tasks.
	TaskTypeProject TaskType = 1
	// TaskTypeHeading represents a heading within a project.
	TaskTypeHeading TaskType = 2
)

func (TaskType) String

func (t TaskType) String() string

String returns the string representation of the TaskType.

type URLCommand

type URLCommand string

URLCommand represents Things URL scheme commands.

const (
	// URLCommandShow opens and shows an item.
	URLCommandShow URLCommand = "show"
	// URLCommandAdd creates a new to-do.
	URLCommandAdd URLCommand = "add"
	// URLCommandAddProject creates a new project.
	URLCommandAddProject URLCommand = "add-project"
	// URLCommandUpdate updates an existing item.
	URLCommandUpdate URLCommand = "update"
	// URLCommandUpdateProject updates an existing project.
	URLCommandUpdateProject URLCommand = "update-project"
	// URLCommandSearch performs a search.
	URLCommandSearch URLCommand = "search"
)

func (URLCommand) String

func (u URLCommand) String() string

String returns the string representation of the URLCommand.

Jump to

Keyboard shortcuts

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