Imported from dan-hart/clings (
AGENTS.md). Install upstream withnpx skills add dan-hart/clings. Copyright stays with the author.
AGENTS.md - clings Project Guidelines
A Things 3 CLI for macOS
Project Overview
clings is a fast, feature-rich command-line interface for Things 3 on macOS, written in Swift.
- License: GNU General Public License v3.0 (GPLv3)
- Platform: macOS only (requires Things 3 installed)
- Technology: Swift + SQLite (reads) + JavaScript for Automation (JXA) via
osascript(writes) - Version: 0.3.1
Build & Run
# Build
swift build
# Run
swift run clings today
swift run clings --help
# Test
swift test
# Build release
swift build -c release
Architecture
Hybrid Read/Write Approach
clings uses a hybrid architecture for optimal performance and safety:
- Reads: Direct SQLite access to the Things 3 database (~30ms response time)
- Writes: JXA/AppleScript through the official Things 3 automation API
This approach provides:
- Near-instant reads without launching Things 3
- Safe writes through the official API
- Compatibility with Things 3 updates
Module Structure
Sources/
├── clings/
│ └── main.swift # Entry point
├── ClingsCore/
│ ├── CLI/
│ │ ├── Commands/ # Command implementations
│ │ └── CLIApp.swift # ArgumentParser setup
│ ├── Database/
│ │ └── ThingsDatabase.swift # SQLite access
│ ├── JXA/
│ │ └── ThingsJXA.swift # JXA script execution
│ ├── Models/
│ │ └── Todo.swift # Data types
│ ├── NLP/
│ │ └── TaskParser.swift # Natural language parsing
│ └── Output/
│ ├── PrettyPrinter.swift
│ └── JSONOutput.swift
Design Principles
- Separation of Concerns: CLI layer thin, business logic in ClingsCore
- Testability: All business logic in library target
- Swift Best Practices: Use Swift's type safety, optionals, and error handling
- Performance: SQLite for reads, batch operations where possible
Code Quality Standards
Error Handling
// Use typed errors
enum ClingsError: LocalizedError {
case thingsNotRunning
case permissionDenied
case notFound(String)
case databaseError(String)
var errorDescription: String? {
switch self {
case .thingsNotRunning:
return "Things 3 is not running"
case .permissionDenied:
return "Automation permission required.\n\nGrant access in System Settings > Privacy & Security > Automation"
case .notFound(let item):
return "Item not found: \(item)"
case .databaseError(let message):
return "Database error: \(message)"
}
}
}
Rules:
- Use Swift's
Errorprotocol for custom error types - Use
throwsandtryfor error propagation - NEVER use force unwrapping (
!) in production code - All error messages must be user-friendly with actionable guidance
- Handle macOS automation permission errors gracefully
Swift Concurrency
Use async/await for I/O operations:
func fetchTodos() async throws -> [Todo] {
try await withCheckedThrowingContinuation { continuation in
// Database or JXA operation
}
}
Formatting
Use SwiftFormat with project defaults. Import organization:
// 1. Foundation/Standard library
import Foundation
// 2. External packages
import ArgumentParser
import GRDB
// 3. Internal modules
import ClingsCore
Testing Requirements
Test Categories
Unit Tests - Test individual functions:
import XCTest
@testable import ClingsCore
final class TaskParserTests: XCTestCase {
func testParseDateTodayReturnsCurrentDate() {
let result = TaskParser.parseDate("today")
XCTAssertEqual(result, Date().formatted(date: .numeric, time: .omitted))
}
}
Integration Tests - Test CLI commands:
import XCTest
final class CLIIntegrationTests: XCTestCase {
func testHelpFlagShowsUsage() throws {
let process = Process()
process.executableURL = URL(fileURLWithPath: ".build/debug/clings")
process.arguments = ["--help"]
let pipe = Pipe()
process.standardOutput = pipe
try process.run()
process.waitUntilExit()
let output = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)
XCTAssertTrue(output?.contains("Things 3") == true)
}
}
Coverage Requirements
- Minimum: 80% overall code coverage
- Critical Paths: 95%+ coverage for:
- Error handling
- Database access
- CLI argument parsing
Dependencies (Package.swift)
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
.package(url: "https://github.com/groue/GRDB.swift", from: "6.24.0"),
.package(url: "https://github.com/malcommac/SwiftDate", from: "7.0.0"),
]
| Package | Purpose |
|---|---|
| swift-argument-parser | CLI argument parsing |
| GRDB.swift | SQLite database access |
| SwiftDate | Date/time parsing and formatting |
CLI Design Guidelines
Command Structure
clings [OPTIONS] <COMMAND>
Options:
--json Output as JSON (for scripting)
--no-color Suppress color output
-h, --help Show help
--version Show version
Commands:
today, t (default) Show today's todos
inbox, i Show inbox todos
upcoming, u Show upcoming todos
anytime Show anytime todos
someday, s Show someday todos
logbook, l Show completed todos
projects List all projects
areas List all areas
tags List all tags
show Show details of a todo by ID
add Quick add with natural language
complete, done Mark a todo as completed
cancel Cancel a todo
delete, rm Delete a todo
update Update a todo's properties
search, find, f Search todos by text
bulk Bulk operations on multiple todos
filter Filter todos using a query
open Open a todo or list in Things 3
stats View productivity statistics
review Interactive weekly review workflow
completions Generate shell completions
Design Principles
- Intuitive: Commands match Things 3 terminology
- Scriptable: JSON output for piping and automation
- Informative: Exit codes indicate success (0), user error (1), system error (2)
- Complete: Shell completions for bash, zsh, fish
- Fast: SQLite reads complete in ~30ms
CI/CD Requirements
GitHub Actions Workflow
Every PR must pass:
swift build- Build succeedsswift test- All tests passswift build -c release- Release build succeeds
Release Process
Before tagging a release:
- Run docs/help preflight:
bash scripts/release-docs-check.sh - Ensure README, help text, and docs are consistent
On tag push:
- Build release binaries (macOS ARM64, macOS x86_64)
- Generate shell completions
- Create GitHub release with artifacts
- Update Homebrew formula
Updating Homebrew Formula
When releasing a new version:
-
Get the SHA256 checksum for the new release tarball:
curl -sL https://github.com/dan-hart/clings/archive/refs/tags/v<VERSION>.tar.gz | shasum -a 256 -
Update the formula in the homebrew-tap repository:
- Repository: https://github.com/dan-hart/homebrew-tap
- File:
Formula/clings.rb - Update the
urlto point to the new version tag - Update the
sha256with the new checksum
-
Commit and push the formula changes
-
Verify the update works:
brew update && brew upgrade clings
Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make changes following these guidelines
- Add tests for new functionality
- Ensure all checks pass:
swift build && swift test - Submit a pull request
License
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
LLM Provider Directives
- Never add LLM Provider as a co-author on commits
- Always update the Homebrew tap when releasing a new version:
- Update version in code
- Run
bash scripts/release-docs-check.shand fix any drift between CLI help, README, and docs - Commit, tag (e.g.,
v0.2.1), and push to intended public remotes only (normallyorigin) - Get SHA256:
curl -sL https://github.com/dan-hart/clings/archive/refs/tags/v<VERSION>.tar.gz | shasum -a 256 - Update your local clone of
homebrew-tap/Formula/clings.rbwith new version and SHA256 - Commit and push homebrew-tap
- Run
brew update && brew upgrade clingsto verify