AGENTS.md
This file guides LLM agents through interacting with and maintaining the lune project—a CLI for the Lunatask productivity app.
Lunatask concepts
Lunatask is end-to-end encrypted and any sensitive information (person/task/note/habit names, contents, etc.) is all omitted. The API only returns basic metadata (IDs, timestamps, status, etc.). That also means there is no way to dynamically list the user's areas, habits, or notebooks; they must copy those details from the desktop app into lune during interactive initialisation or reconfiguration.
Commands
Run these most to least often:
task fmt lint:fix staticcheck test # Important ones to run often
task # Include vulnerability and copyright checks
The Taskfile auto-installs tools (gofumpt, staticcheck, govulncheck) when running their respective tasks.
Architecture
main.go → cmd.Execute() entrypoint
cmd/
root.go → Root command, registers all subcommands and groups
init/ → Interactive setup wizard (huh forms, multi-step navigation)
ping.go → Token verification command
add.go, done.go, jrnl.go → Shortcuts (delegate to resource commands)
task/ → task add/list/show/update/delete
note/ → note add/list/show/update/delete
person/ → person add/list/show/update/delete/timeline
area/ → area list/show (read-only)
goal/ → goal list/show (read-only)
journal/ → journal add
habit/ → habit track
mcp/ → MCP server command (stdio/sse/http transports)
internal/
client/ → Lunatask API client factory (uses system keyring)
config/ → TOML config at ~/.config/lune/config.toml
completion/ → Shell completion helpers (config-based + static values)
dateutil/ → Natural language date parsing via go-dateparser
stats/ → Usage statistics helpers
ui/ → Lipgloss styles (Success, Warning, Error, H1, H2, FormatDate)
validate/ → Input validation (delegates to go-lunatask Parse* functions)
mcp/
shared/ → Provider types (AreaProvider, HabitProvider) and error helpers
tools/ → MCP tool handlers (crud/, habit/, timeline/, timestamp/)
resources/ → MCP resource handlers (areas/, habits/, notebooks/, task/, note/, person/)
Command flow: fang.Execute() wraps Cobra with version/commit info and
signal handling. Resource commands live in subpackages (cmd/task/); shortcuts
in cmd/ delegate to resource commands by calling their RunE directly and
copying their flagset.
API client pattern: client.New() returns a *lunatask.Client. Create
operations use builders: client.NewTask(name).InArea(id).WithStatus(s).Create(ctx).
Update operations use separate builders: client.UpdateTask(id).WithStatus(s).Update(ctx).
Command patterns
Resource commands (task, note, person, etc.):
- Parent command in
<resource>.gowithGroupID: "resources" - Subcommands (add, list, get, update, delete) in separate files
- Export the
*Cmdvars for shortcuts to reference
Shortcuts (add, done, jrnl):
- Live in
cmd/directly withGroupID: "shortcuts" - Copy flags from resource command:
addCmd.Flags().AddFlagSet(task.AddCmd.Flags()) - Call resource command's
RunEdirectly
Shell completions registered inline via RegisterFlagCompletionFunc;
see cmd/task/add.go:56-63 for examples. Use completion.Areas, completion.Goals,
etc. for config-based completions, or completion.Static("val1", "val2") for
fixed options.
MCP server patterns
The lune mcp command starts a Model Context Protocol server for LLM tool
integration. Architecture:
- Resources (
internal/mcp/resources/): Read-only data from config (areas, habits, notebooks). Each has aHandlerwithHandleReadmethod. - Tools (
internal/mcp/tools/): Actions that call the Lunatask API. Each tool uses*Input/*Outputstructs withjsonschema:"required"tags for MCP schema generation. - Shared (
internal/mcp/shared/): Provider types decouple config from MCP handlers;ErrorResult()returns structured tool errors.
Consolidated tools (7 total):
- CRUD tools (
crud/):create,update,delete,query— each accepts anentityfield to specify the type (task, note, person, journal, area, etc.) - Action tools:
track_habit(habit/),add_timeline_note(timeline/),get_timestamp(timestamp/)
Tool handler pattern: Tools use mcp.AddTool() with typed input structs.
Validation happens in parse*Input() functions returning *mcp.CallToolResult
for errors (not Go errors). Parse functions call lunatask.Parse*() for enums
and dateutil.Parse() for dates.
Config-driven tools: cfg.MCP.Tools.* booleans enable/disable individual
tools. All default to enabled via cfg.MCP.MCPDefaults() (except query, which
is a fallback for agents without MCP resource support).
Core dependencies
Reference docs via context7 or doc-agent when unsure:
git.secluded.site/go-lunatask(context7:/websites/pkg_go_dev_git_secluded_site_go-lunatask): Lunatask API client. Amolith maintains it, so issues can be resolved or missing features added quickly.github.com/charmbracelet/huh(context7:/charmbracelet/huh): powerful and attractive libs for interactive forms and promptsgithub.com/charmbracelet/lipgloss(context7:/charmbracelet/lipgloss): Terminal styling.github.com/charmbracelet/fang(context7:/charmbracelet/fang): Cobra wrapper with fancy styling, version injection, signal handling.github.com/zalando/go-keyring(context7:/zalando/go-keyring): Cross-platform interface to the system keyringgithub.com/BurntSushi/toml(context7:/burntsushi/toml): Config file parsing.github.com/spf13/cobra(context7:/websites/pkg_go_dev_github_com_spf13_cobra): CLI framework.github.com/markusmobius/go-dateparser(context7:/markusmobius/go-dateparser): Natural language date parsing.github.com/modelcontextprotocol/go-sdk/mcp(context7:/modelcontextprotocol/go-sdk): MCP server SDK for LLM tool integration.
Gotchas
Enum validation
Use validate.TaskStatus(), validate.Motivation(), validate.RelationshipStrength()
to parse and validate string inputs to lunatask.* types. These normalize
case and return typed values or wrapped errors (ErrInvalidStatus, etc.).
Date parsing
dateutil.Parse() uses PHP strtotime syntax ("yesterday", "-2 days", "next Monday")
and ISO dates ("2024-01-15"). Empty input returns today's date. Returns
lunatask.Date type.
Stdin convention
Many commands accept - as an argument or flag value to read from stdin:
task add -reads task name from first stdin line--note -reads entire stdin as the note content
The implementations differ: name uses bufio.Scanner (single line), note uses
os.ReadFile("/dev/stdin") (all content).
Deep link support
Commands accepting IDs also accept lunatask:// deep links. Use
validate.Reference() to normalize either format to a UUID. Deep link
parsing and building use lunatask.ParseDeepLink() and lunatask.BuildDeepLink()
from go-lunatask.
Linter exclusions for cmd/
The .golangci.yaml disables several linters for cmd/:
gochecknoglobals,gochecknoinits: Cobra requires package-level vars andinit()errcheck,godox: Stub implementations deliberately print without checking, TODOs are placeholderswrapcheck: CLI returns errors for display; wrapping adds noisedupl: Builder types (TaskBuilder,TaskUpdateBuilder) share method signatures but differ in type; duplication is structural
These patterns are intentional.
Config key lookups
Areas, goals, notebooks, and habits are stored with user-defined key fields
(short aliases like "work", "personal"). Commands accept keys, not UUIDs.
Lookup methods: cfg.AreaByKey(), area.GoalByKey(), cfg.NotebookByKey(),
cfg.HabitByKey(). There are also *ByID() methods for reverse lookups.
Goals require their parent area context—use cfg.FindGoalsByKey() to find goals
across all areas, which returns []GoalMatch{Goal, Area} pairs.
Access token
Read from system keyring via internal/client.New(). No interactive
prompts—fail fast with client.ErrNoToken if not configured.
Output formatting
Use cmd.OutOrStdout() and cmd.ErrOrStderr() for testability. Styles are in
internal/ui/styles.go—use ui.Success, ui.Error, etc. rather than inline
colors.
Accessibility: Avoid dim text (ANSI color 8)—it's unreadable in many terminal color schemes. Use italic or other formatting for de-emphasized text instead.
Testing
Table-driven tests with t.Parallel(). Use _test package suffix for black-box
testing (see cmd/init/ui_test.go). When testing commands, use cmd.SetOut() /
cmd.SetErr() to capture output.
Init wizard
The cmd/init/ package implements a multi-step wizard using huh forms.
Navigation uses wizardNav (navNext/navBack/navQuit) with sentinel errors
(errQuit, errReset). Steps are functions returning navigation direction;
the wizard loops through them allowing back/forward navigation.