lune
created pr with
ps-216
changed status to
closed
Closing as superceded by 126
cmds
checkout latest patchset:
ssh pr.pico.sh print pr-124 | git am -3checkout any patchset in a patch request:
ssh pr.pico.sh print ps-X | git am -3add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 124set PR to open (enables RSS notifications):
ssh pr.pico.sh pr open 124set PR to draft (stops RSS notifications):
ssh pr.pico.sh pr draft 124
Patchset
ps-216
→ listing real name of tasks
juan
construction of db for management of decrypted names of tasks
2026-04-21T03:21:16Zjuan
fix UTF8 chracteres
2026-04-21T05:43:09Zjuan
filter by status and coloring types of status
2026-04-21T21:13:24Zjuan
update help
2026-04-21T21:18:53Zjuan
list areas names
2026-04-21T21:21:36Zjuan
adjust table tasks to truncate name and list area
2026-04-21T21:44:53Zjuan
partial implementation of listing tasks by areas
2026-04-21T21:56:40Zjuan
final implementation of listing tasks by areas with UTF-8 chracteres
2026-04-21T22:05:20Zjuan
sort on task list to show first do now
2026-04-21T22:10:20Zjuan
update with 8 char of id and db.go
2026-04-21T22:33:29Zjuan
partial implementation of TUI menu on task update
2026-04-21T23:27:54Zjuan
task name correcion on TUI menu of Update task
2026-04-21T23:35:35Zjuan
TUI menu for update task with others flags
2026-04-21T23:38:19Zjuan
helper for TUI update task
2026-04-21T23:43:02Zjuan
search by first 8 char of note id for editing it
2026-04-21T23:49:56Zjuan
partial implementation on note TUI menu for update
2026-04-22T00:00:17Zjuan
TUI menu notebook just with tags
2026-04-22T00:05:46Zjuan
Note update now let you insert only the first 8 char of notebook id in update
2026-04-22T00:20:55Zjuan
implementation of (lune note notebook-list) for listing all notebooks and its ID
2026-04-22T00:25:53Zjuan
partial implementation of lune note show-content ID
2026-04-22T01:10:51Zjuan
convert \n in new line and \ in space for lune note show-content ID
2026-04-22T02:01:11Zjuan
2026-04-22T02:18:42Z
listing real name of tasks
juan
2026-04-21T03:21:16ZSemantic diff summary
14 added,
12 modified,
0 signature changed,
6 removed
across 6 analyzed files
(2 files skipped: unsupported file type)
cmd/task/list.go
-
chunklines 14-20modified -
type_declarationenrichedTaskadded -
function_declarationrunListmodified -
function_declarationoutputJSONEnrichedadded -
function_declarationapplyFiltersEnrichedadded -
function_declarationapplyFiltersremoved -
function_declarationoutputJSONremoved -
function_declarationoutputTableEnrichedadded -
function_declarationoutputTableremoved
internal/client/client.go
-
chunklines 6-15modified -
function_declarationGetTokenmodified -
function_declarationSetTokenmodified -
type_declarationSecretStoreadded -
function_declarationNewSecretStoreadded -
method_declarationSetadded -
method_declarationGetadded -
function_declarationDeleteTokenmodified -
function_declarationHasKeyringTokenmodified
+25
-0
cmd/init/apikey.go
#
@@ -16,6 +16,7 @@ import (
"github.com/spf13/cobra"
"git.secluded.site/lune/internal/client"
+ "git.secluded.site/lune/internal/db"
"git.secluded.site/lune/internal/ui"
)
@@ -219,3 +220,27 @@ func validateTokenWithPing(token string) error {
return err
}
+
+func runMasterPasswordStep(cmd *cobra.Command) wizardNav {
+ var password string
+ err := huh.NewInput().
+ Title("Lunatask Master Password").
+ Description("Optional: provide your master password to enable local database decryption (stored securely).").
+ EchoMode(huh.EchoModePassword).
+ Value(&password).
+ Run()
+ if err != nil {
+ if errors.Is(err, huh.ErrUserAborted) {
+ return navQuit
+ }
+ return navNext // Continue anyway
+ }
+
+ if password != "" {
+ if err := db.SetMasterPassword(password); err != nil {
+ fmt.Fprintln(cmd.OutOrStdout(), ui.Error.Render("Failed to save master password: "+err.Error()))
+ }
+ }
+
+ return navNext
+}
+3
-0
cmd/init/init.go
#
@@ -120,6 +120,7 @@ func runFreshSetup(cmd *cobra.Command, cfg *config.Config) error {
func() wizardNav { return runHabitsStep(cfg) },
func() wizardNav { return runDefaultsStep(cfg) },
func() wizardNav { return runAccessTokenStep(cmd) },
+ func() wizardNav { return runMasterPasswordStep(cmd) },
}
step := 0
@@ -173,6 +174,7 @@ func runReconfigure(cmd *cobra.Command, cfg *config.Config) error {
"defaults": func() error { return configureDefaults(cfg) },
"ui": func() error { return configureUIPrefs(cfg) },
"apikey": func() error { return configureAccessToken(cmd) },
+ "password": func() error { runMasterPasswordStep(cmd); return nil },
"reset": func() error { return resetConfig(cmd, cfg) },
}
@@ -188,6 +190,7 @@ func runReconfigure(cmd *cobra.Command, cfg *config.Config) error {
huh.NewOption("Set defaults", "defaults"),
huh.NewOption("UI preferences", "ui"),
huh.NewOption("Access token", "apikey"),
+ huh.NewOption("Master password (for LocalDB)", "password"),
huh.NewOption("Reset all configuration", "reset"),
huh.NewOption("Done", choiceDone),
).
+34
-21
cmd/note/list.go
#
@@ -13,12 +13,18 @@ import (
"git.secluded.site/lune/internal/client"
"git.secluded.site/lune/internal/completion"
"git.secluded.site/lune/internal/config"
+ "git.secluded.site/lune/internal/db"
"git.secluded.site/lune/internal/ui"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/table"
"github.com/spf13/cobra"
)
+type enrichedNote struct {
+ lunatask.Note
+ Name string `json:"name,omitempty"`
+}
+
// ListCmd lists notes. Exported for potential use by shortcuts.
var ListCmd = &cobra.Command{
Use: "list",
@@ -54,26 +60,42 @@ func runList(cmd *cobra.Command, _ []string) error {
return err
}
+ cfg, _ := config.Load()
+ enriched := make([]enrichedNote, 0, len(notes))
+ for _, n := range notes {
+ name := ""
+ if cfg != nil && cfg.Experimental.LocalDB {
+ name, _ = db.EnrichTask(n.ID) // EnrichTask is generic enough for IDs
+ }
+ enriched = append(enriched, enrichedNote{Note: n, Name: name})
+ }
+
notebookID, err := resolveNotebookFilter(cmd)
if err != nil {
return err
}
+ filtered := enriched
if notebookID != "" {
- notes = filterByNotebook(notes, notebookID)
+ filtered = make([]enrichedNote, 0)
+ for _, en := range enriched {
+ if en.NotebookID != nil && *en.NotebookID == notebookID {
+ filtered = append(filtered, en)
+ }
+ }
}
- if len(notes) == 0 {
+ if len(filtered) == 0 {
fmt.Fprintln(cmd.OutOrStdout(), "No notes found")
return nil
}
if mustGetBoolFlag(cmd, "json") {
- return outputJSON(cmd, notes)
+ return outputJSONEnriched(cmd, filtered)
}
- return outputTable(cmd, notes)
+ return outputTableEnriched(cmd, filtered)
}
func buildListOptions(cmd *cobra.Command) *lunatask.ListNotesOptions {
@@ -119,18 +141,6 @@ func resolveNotebookFilter(cmd *cobra.Command) (string, error) {
return notebook.ID, nil
}
-func filterByNotebook(notes []lunatask.Note, notebookID string) []lunatask.Note {
- filtered := make([]lunatask.Note, 0, len(notes))
-
- for _, note := range notes {
- if note.NotebookID != nil && *note.NotebookID == notebookID {
- filtered = append(filtered, note)
- }
- }
-
- return filtered
-}
-
func mustGetStringFlag(cmd *cobra.Command, name string) string {
f := cmd.Flags().Lookup(name)
if f == nil {
@@ -149,7 +159,7 @@ func mustGetBoolFlag(cmd *cobra.Command, name string) bool {
return f.Value.String() == "true"
}
-func outputJSON(cmd *cobra.Command, notes []lunatask.Note) error {
+func outputJSONEnriched(cmd *cobra.Command, notes []enrichedNote) error {
enc := json.NewEncoder(cmd.OutOrStdout())
enc.SetIndent("", " ")
@@ -160,7 +170,7 @@ func outputJSON(cmd *cobra.Command, notes []lunatask.Note) error {
return nil
}
-func outputTable(cmd *cobra.Command, notes []lunatask.Note) error {
+func outputTableEnriched(cmd *cobra.Command, notes []enrichedNote) error {
cfg, _ := config.Load()
rows := make([][]string, 0, len(notes))
@@ -185,13 +195,16 @@ func outputTable(cmd *cobra.Command, notes []lunatask.Note) error {
pinned = "📌"
}
- created := ui.FormatDate(note.CreatedAt)
+ name := note.Name
+ if name == "" {
+ name = note.ID[:8] + "..."
+ }
- rows = append(rows, []string{note.ID, notebook, dateOn, pinned, created})
+ rows = append(rows, []string{name, notebook, dateOn, pinned})
}
tbl := table.New().
- Headers("ID", "NOTEBOOK", "DATE", "📌", "CREATED").
+ Headers("NAME", "NOTEBOOK", "DATE", "📌").
Rows(rows...).
StyleFunc(func(row, col int) lipgloss.Style {
if row == table.HeaderRow {
+48
-13
cmd/task/list.go
#
@@ -14,6 +14,7 @@ import (
"git.secluded.site/lune/internal/client"
"git.secluded.site/lune/internal/completion"
"git.secluded.site/lune/internal/config"
+ "git.secluded.site/lune/internal/db"
"git.secluded.site/lune/internal/ui"
"git.secluded.site/lune/internal/validate"
"github.com/charmbracelet/lipgloss"
@@ -21,6 +22,11 @@ import (
"github.com/spf13/cobra"
)
+type enrichedTask struct {
+ lunatask.Task
+ Name string `json:"name,omitempty"`
+}
+
// ErrUnknownArea indicates the specified area key was not found in config.
var ErrUnknownArea = errors.New("unknown area key")
@@ -61,6 +67,16 @@ func runList(cmd *cobra.Command, _ []string) error {
return err
}
+ cfg, _ := config.Load()
+ enriched := make([]enrichedTask, 0, len(tasks))
+ for _, t := range tasks {
+ name := ""
+ if cfg != nil && cfg.Experimental.LocalDB {
+ name, _ = db.EnrichTask(t.ID)
+ }
+ enriched = append(enriched, enrichedTask{Task: t, Name: name})
+ }
+
areaID, err := resolveAreaFilter(cmd)
if err != nil {
return err
@@ -72,19 +88,19 @@ func runList(cmd *cobra.Command, _ []string) error {
}
showAll := mustGetBoolFlag(cmd, "all")
- tasks = applyFilters(tasks, areaID, statusFilter, showAll)
+ filtered := applyFiltersEnriched(enriched, areaID, statusFilter, showAll)
- if len(tasks) == 0 {
+ if len(filtered) == 0 {
fmt.Fprintln(cmd.OutOrStdout(), "No tasks found")
return nil
}
if mustGetBoolFlag(cmd, "json") {
- return outputJSON(cmd, tasks)
+ return outputJSONEnriched(cmd, filtered)
}
- return outputTable(cmd, tasks)
+ return outputTableEnriched(cmd, filtered)
}
// mustGetStringFlag returns the string flag value. Panics if flag doesn't exist
@@ -156,25 +172,41 @@ func resolveStatusFilter(cmd *cobra.Command) (string, error) {
return string(s), nil
}
-func applyFilters(tasks []lunatask.Task, areaID, statusFilter string, showAll bool) []lunatask.Task {
+func applyFiltersEnriched(tasks []enrichedTask, areaID, statusFilter string, showAll bool) []enrichedTask {
+ // Extract basic tasks for library filter
+ baseTasks := make([]lunatask.Task, len(tasks))
+ for i, t := range tasks {
+ baseTasks[i] = t.Task
+ }
+
opts := &lunatask.TaskFilterOptions{
IncludeCompleted: showAll,
Today: time.Now(),
}
-
if areaID != "" {
opts.AreaID = &areaID
}
-
if statusFilter != "" {
s := lunatask.TaskStatus(statusFilter)
opts.Status = &s
}
- return lunatask.FilterTasks(tasks, opts)
+ filteredBase := lunatask.FilterTasks(baseTasks, opts)
+
+ // Map back to enriched
+ res := make([]enrichedTask, 0, len(filteredBase))
+ for _, fb := range filteredBase {
+ for _, et := range tasks {
+ if et.ID == fb.ID {
+ res = append(res, et)
+ break
+ }
+ }
+ }
+ return res
}
-func outputJSON(cmd *cobra.Command, tasks []lunatask.Task) error {
+func outputJSONEnriched(cmd *cobra.Command, tasks []enrichedTask) error {
enc := json.NewEncoder(cmd.OutOrStdout())
enc.SetIndent("", " ")
@@ -185,7 +217,7 @@ func outputJSON(cmd *cobra.Command, tasks []lunatask.Task) error {
return nil
}
-func outputTable(cmd *cobra.Command, tasks []lunatask.Task) error {
+func outputTableEnriched(cmd *cobra.Command, tasks []enrichedTask) error {
rows := make([][]string, 0, len(tasks))
for _, task := range tasks {
@@ -199,13 +231,16 @@ func outputTable(cmd *cobra.Command, tasks []lunatask.Task) error {
scheduled = ui.FormatDate(task.ScheduledOn.Time)
}
- created := ui.FormatDate(task.CreatedAt)
+ name := task.Name
+ if name == "" {
+ name = task.ID[:8] + "..."
+ }
- rows = append(rows, []string{task.ID, status, scheduled, created})
+ rows = append(rows, []string{name, status, scheduled})
}
tbl := table.New().
- Headers("ID", "STATUS", "SCHEDULED", "CREATED").
+ Headers("NAME", "STATUS", "SCHEDULED").
Rows(rows...).
StyleFunc(func(row, col int) lipgloss.Style {
if row == table.HeaderRow {
+2
-0
go.mod
#
@@ -47,6 +47,7 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/godbus/dbus/v5 v5.2.2 // indirect
+ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
@@ -62,6 +63,7 @@ require (
github.com/muesli/termenv v0.16.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.10 // indirect
+ github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
+17
-0
go.sum
#
@@ -73,10 +73,14 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
@@ -85,6 +89,7 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/klauspost/lctime v0.1.0 h1:nINsuFc860M9cyYhT6vfg6U1USh7kiVBj/s/2b04U70=
@@ -115,6 +120,9 @@ github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -129,6 +137,8 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
@@ -140,18 +150,25 @@ golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+59
-30
internal/client/client.go
#
@@ -6,9 +6,10 @@
package client
import (
+ "encoding/json"
"errors"
- "fmt"
"os"
+ "path/filepath"
"runtime/debug"
"git.secluded.site/go-lunatask"
@@ -39,58 +40,86 @@ func New() (*lunatask.Client, error) {
}
// GetToken returns the access token from LUNE_ACCESS_TOKEN environment variable
-// or keyring. Returns empty string and nil error if not found in either location;
-// returns error for keyring access problems. Environment variable takes precedence.
+// or keyring/secret store. Environment variable takes precedence.
func GetToken() (string, error) {
- // Env var takes precedence for explicit override
if token := os.Getenv("LUNE_ACCESS_TOKEN"); token != "" {
return token, nil
}
token, err := keyring.Get(keyringService, keyringUser)
- if err != nil {
- if errors.Is(err, keyring.ErrNotFound) {
- return "", nil
- }
-
- return "", fmt.Errorf("accessing system keyring: %w", err)
+ if err == nil {
+ return token, nil
}
- return token, nil
+ // Keyring failed, try SecretStore
+ store := NewSecretStore()
+ return store.Get(keyringUser)
}
-// SetToken stores the access token in the system keyring.
+// SetToken stores the access token in the system keyring or SecretStore.
func SetToken(token string) error {
- if err := keyring.Set(keyringService, keyringUser, token); err != nil {
- return fmt.Errorf("keyring set: %w", err)
+ err := keyring.Set(keyringService, keyringUser, token)
+ if err == nil {
+ return nil
}
- return nil
+ // Keyring failed, save to SecretStore
+ store := NewSecretStore()
+ return store.Set(keyringUser, token)
}
-// DeleteToken removes the access token from the system keyring.
-func DeleteToken() error {
- if err := keyring.Delete(keyringService, keyringUser); err != nil {
- return fmt.Errorf("keyring delete: %w", err)
+// SecretStore implementation moved to client for simplicity or kept in db package.
+// For now, I'll add a minimal version here or import it correctly.
+type SecretStore struct {
+ Path string
+}
+
+func NewSecretStore() *SecretStore {
+ home, _ := os.UserHomeDir()
+ return &SecretStore{
+ Path: filepath.Join(home, ".config", "lune", "secrets.json"),
}
+}
- return nil
+func (s *SecretStore) Set(key, value string) error {
+ data := make(map[string]string)
+ if f, err := os.ReadFile(s.Path); err == nil {
+ json.Unmarshal(f, &data)
+ }
+ data[key] = value
+ f, _ := json.Marshal(data)
+ os.MkdirAll(filepath.Dir(s.Path), 0700)
+ return os.WriteFile(s.Path, f, 0600)
}
-// HasKeyringToken checks if an access token is stored in the keyring.
-// Returns (true, nil) if found, (false, nil) if not found,
-// or (false, error) if there was a keyring access problem.
-func HasKeyringToken() (bool, error) {
- _, err := keyring.Get(keyringService, keyringUser)
+func (s *SecretStore) Get(key string) (string, error) {
+ f, err := os.ReadFile(s.Path)
if err != nil {
- if errors.Is(err, keyring.ErrNotFound) {
- return false, nil
- }
+ return "", nil
+ }
+ data := make(map[string]string)
+ json.Unmarshal(f, &data)
+ return data[key], nil
+}
+
+// DeleteToken removes the access token from the system keyring and SecretStore.
+func DeleteToken() error {
+ keyring.Delete(keyringService, keyringUser)
+ store := NewSecretStore()
+ return store.Set(keyringUser, "")
+}
- return false, fmt.Errorf("accessing system keyring: %w", err)
+// HasKeyringToken checks if an access token is stored in the keyring or SecretStore.
+func HasKeyringToken() (bool, error) {
+ _, err := keyring.Get(keyringService, keyringUser)
+ if err == nil {
+ return true, nil
}
- return true, nil
+ // Keyring failed or not found, try SecretStore
+ store := NewSecretStore()
+ token, _ := store.Get(keyringUser)
+ return token != "", nil
}
// version returns the module version from build info, or "dev" if unavailable.
+18
-6
internal/config/config.go
#
@@ -20,12 +20,24 @@ var ErrNotFound = errors.New("config file not found")
// Config represents the lune configuration file structure.
type Config struct {
- UI UIConfig `toml:"ui"`
- Defaults Defaults `toml:"defaults"`
- MCP MCPConfig `toml:"mcp"`
- Areas []Area `toml:"areas"`
- Notebooks []Notebook `toml:"notebooks"`
- Habits []Habit `toml:"habits"`
+ UI UIConfig `toml:"ui"`
+ Defaults Defaults `toml:"defaults"`
+ MCP MCPConfig `toml:"mcp"`
+ Experimental ExperimentalConfig `toml:"experimental"`
+ Areas []Area `toml:"areas"`
+ Notebooks []Notebook `toml:"notebooks"`
+ Habits []Habit `toml:"habits"`
+}
+
+// ExperimentalConfig holds experimental features.
+type ExperimentalConfig struct {
+ LocalDB bool `toml:"local_db"`
+}
+
+// ApplyDefaults enables all tools if none are explicitly configured.
+func (c *Config) ApplyDefaults() {
+ c.MCP.MCPDefaults()
+ // Other defaults can go here
}
// MCPConfig holds MCP server settings.