lune

created pr with 124.1 on 2026-04-22T02:46:44Z · by 613b58b7
changed status to closed on 2026-06-21T16:45:31Z · by 66ddd677
Closing as superceded by 126
cmds
checkout latest patchset:
ssh pr.pico.sh print 124 | git am -3
checkout any patchset in a patch request:
ssh pr.pico.sh print 124.[rev] | git am -3
add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 124
set PR to open (enables RSS notifications):
ssh pr.pico.sh pr open 124
set PR to draft (stops RSS notifications):
ssh pr.pico.sh pr draft 124

Patchset 124.1 on 2026-04-22T02:46:44Z · commit c03fc8d

listing real name of tasks
juan 2026-04-21T03:21:16Z
construction of db for management of decrypted names of tasks
juan 2026-04-21T05:43:09Z
fix UTF8 chracteres
juan 2026-04-21T21:13:24Z
filter by status and coloring types of status
juan 2026-04-21T21:18:53Z
update help
juan 2026-04-21T21:21:36Z
list areas names
juan 2026-04-21T21:44:53Z
adjust table tasks to truncate name and list area
juan 2026-04-21T21:56:40Z
partial implementation of listing tasks by areas
juan 2026-04-21T22:05:20Z
final implementation of listing tasks by areas with UTF-8 chracteres
juan 2026-04-21T22:10:20Z
sort on task list to show first do now
juan 2026-04-21T22:33:29Z
update with 8 char of id and db.go
juan 2026-04-21T23:27:54Z
partial implementation of TUI menu on task update
juan 2026-04-21T23:35:35Z
task name correcion on TUI menu of Update task
juan 2026-04-21T23:38:19Z
TUI menu for update task with others flags
juan 2026-04-21T23:43:02Z
helper for TUI update task
juan 2026-04-21T23:49:56Z
search by first 8 char of note id for editing it
juan 2026-04-22T00:00:17Z
partial implementation on note TUI menu for update
juan 2026-04-22T00:05:46Z
TUI menu notebook just with tags
juan 2026-04-22T00:20:55Z
Note update now let you insert only the first 8 char of notebook id in update
juan 2026-04-22T00:25:53Z
implementation of (lune note notebook-list) for listing all notebooks and its ID
juan 2026-04-22T01:10:51Z
partial implementation of lune note show-content ID
juan 2026-04-22T02:01:11Z
convert \n in new line and \ in space for lune note show-content ID
juan 2026-04-22T02:18:42Z
listing real name of tasks
juan 2026-04-21T03:21:16Z
Semantic diff summary
14 added, 12 modified, 0 signature changed, 6 removed across 6 analyzed files (2 files skipped: unsupported file type)
+25 -0 cmd/init/apikey.go #
......@@ -16,6 +16,7 @@ import (
1616 "github.com/spf13/cobra"
1717
1818 "git.secluded.site/lune/internal/client"
19+ "git.secluded.site/lune/internal/db"
1920 "git.secluded.site/lune/internal/ui"
2021 )
2122
......@@ -219,3 +220,27 @@ func validateTokenWithPing(token string) error {
219220
220221 return err
221222 }
223+
224+func runMasterPasswordStep(cmd *cobra.Command) wizardNav {
225+ var password string
226+ err := huh.NewInput().
227+ Title("Lunatask Master Password").
228+ Description("Optional: provide your master password to enable local database decryption (stored securely).").
229+ EchoMode(huh.EchoModePassword).
230+ Value(&password).
231+ Run()
232+ if err != nil {
233+ if errors.Is(err, huh.ErrUserAborted) {
234+ return navQuit
235+ }
236+ return navNext // Continue anyway
237+ }
238+
239+ if password != "" {
240+ if err := db.SetMasterPassword(password); err != nil {
241+ fmt.Fprintln(cmd.OutOrStdout(), ui.Error.Render("Failed to save master password: "+err.Error()))
242+ }
243+ }
244+
245+ return navNext
246+}
+3 -0 cmd/init/init.go #
......@@ -120,6 +120,7 @@ func runFreshSetup(cmd *cobra.Command, cfg *config.Config) error {
120120 func() wizardNav { return runHabitsStep(cfg) },
121121 func() wizardNav { return runDefaultsStep(cfg) },
122122 func() wizardNav { return runAccessTokenStep(cmd) },
123+ func() wizardNav { return runMasterPasswordStep(cmd) },
123124 }
124125
125126 step := 0
......@@ -173,6 +174,7 @@ func runReconfigure(cmd *cobra.Command, cfg *config.Config) error {
173174 "defaults": func() error { return configureDefaults(cfg) },
174175 "ui": func() error { return configureUIPrefs(cfg) },
175176 "apikey": func() error { return configureAccessToken(cmd) },
177+ "password": func() error { runMasterPasswordStep(cmd); return nil },
176178 "reset": func() error { return resetConfig(cmd, cfg) },
177179 }
178180
......@@ -188,6 +190,7 @@ func runReconfigure(cmd *cobra.Command, cfg *config.Config) error {
188190 huh.NewOption("Set defaults", "defaults"),
189191 huh.NewOption("UI preferences", "ui"),
190192 huh.NewOption("Access token", "apikey"),
193+ huh.NewOption("Master password (for LocalDB)", "password"),
191194 huh.NewOption("Reset all configuration", "reset"),
192195 huh.NewOption("Done", choiceDone),
193196 ).
+34 -21 cmd/note/list.go #
......@@ -13,12 +13,18 @@ import (
1313 "git.secluded.site/lune/internal/client"
1414 "git.secluded.site/lune/internal/completion"
1515 "git.secluded.site/lune/internal/config"
16+ "git.secluded.site/lune/internal/db"
1617 "git.secluded.site/lune/internal/ui"
1718 "github.com/charmbracelet/lipgloss"
1819 "github.com/charmbracelet/lipgloss/table"
1920 "github.com/spf13/cobra"
2021 )
2122
23+type enrichedNote struct {
24+ lunatask.Note
25+ Name string `json:"name,omitempty"`
26+}
27+
2228 // ListCmd lists notes. Exported for potential use by shortcuts.
2329 var ListCmd = &cobra.Command{
2430 Use: "list",
......@@ -54,26 +60,42 @@ func runList(cmd *cobra.Command, _ []string) error {
5460 return err
5561 }
5662
63+ cfg, _ := config.Load()
64+ enriched := make([]enrichedNote, 0, len(notes))
65+ for _, n := range notes {
66+ name := ""
67+ if cfg != nil && cfg.Experimental.LocalDB {
68+ name, _ = db.EnrichTask(n.ID) // EnrichTask is generic enough for IDs
69+ }
70+ enriched = append(enriched, enrichedNote{Note: n, Name: name})
71+ }
72+
5773 notebookID, err := resolveNotebookFilter(cmd)
5874 if err != nil {
5975 return err
6076 }
6177
78+ filtered := enriched
6279 if notebookID != "" {
63- notes = filterByNotebook(notes, notebookID)
80+ filtered = make([]enrichedNote, 0)
81+ for _, en := range enriched {
82+ if en.NotebookID != nil && *en.NotebookID == notebookID {
83+ filtered = append(filtered, en)
84+ }
85+ }
6486 }
6587
66- if len(notes) == 0 {
88+ if len(filtered) == 0 {
6789 fmt.Fprintln(cmd.OutOrStdout(), "No notes found")
6890
6991 return nil
7092 }
7193
7294 if mustGetBoolFlag(cmd, "json") {
73- return outputJSON(cmd, notes)
95+ return outputJSONEnriched(cmd, filtered)
7496 }
7597
76- return outputTable(cmd, notes)
98+ return outputTableEnriched(cmd, filtered)
7799 }
78100
79101 func buildListOptions(cmd *cobra.Command) *lunatask.ListNotesOptions {
......@@ -119,18 +141,6 @@ func resolveNotebookFilter(cmd *cobra.Command) (string, error) {
119141 return notebook.ID, nil
120142 }
121143
122-func filterByNotebook(notes []lunatask.Note, notebookID string) []lunatask.Note {
123- filtered := make([]lunatask.Note, 0, len(notes))
124-
125- for _, note := range notes {
126- if note.NotebookID != nil && *note.NotebookID == notebookID {
127- filtered = append(filtered, note)
128- }
129- }
130-
131- return filtered
132-}
133-
134144 func mustGetStringFlag(cmd *cobra.Command, name string) string {
135145 f := cmd.Flags().Lookup(name)
136146 if f == nil {
......@@ -149,7 +159,7 @@ func mustGetBoolFlag(cmd *cobra.Command, name string) bool {
149159 return f.Value.String() == "true"
150160 }
151161
152-func outputJSON(cmd *cobra.Command, notes []lunatask.Note) error {
162+func outputJSONEnriched(cmd *cobra.Command, notes []enrichedNote) error {
153163 enc := json.NewEncoder(cmd.OutOrStdout())
154164 enc.SetIndent("", " ")
155165
......@@ -160,7 +170,7 @@ func outputJSON(cmd *cobra.Command, notes []lunatask.Note) error {
160170 return nil
161171 }
162172
163-func outputTable(cmd *cobra.Command, notes []lunatask.Note) error {
173+func outputTableEnriched(cmd *cobra.Command, notes []enrichedNote) error {
164174 cfg, _ := config.Load()
165175 rows := make([][]string, 0, len(notes))
166176
......@@ -185,13 +195,16 @@ func outputTable(cmd *cobra.Command, notes []lunatask.Note) error {
185195 pinned = "📌"
186196 }
187197
188- created := ui.FormatDate(note.CreatedAt)
198+ name := note.Name
199+ if name == "" {
200+ name = note.ID[:8] + "..."
201+ }
189202
190- rows = append(rows, []string{note.ID, notebook, dateOn, pinned, created})
203+ rows = append(rows, []string{name, notebook, dateOn, pinned})
191204 }
192205
193206 tbl := table.New().
194- Headers("ID", "NOTEBOOK", "DATE", "📌", "CREATED").
207+ Headers("NAME", "NOTEBOOK", "DATE", "📌").
195208 Rows(rows...).
196209 StyleFunc(func(row, col int) lipgloss.Style {
197210 if row == table.HeaderRow {
+48 -13 cmd/task/list.go #
......@@ -14,6 +14,7 @@ import (
1414 "git.secluded.site/lune/internal/client"
1515 "git.secluded.site/lune/internal/completion"
1616 "git.secluded.site/lune/internal/config"
17+ "git.secluded.site/lune/internal/db"
1718 "git.secluded.site/lune/internal/ui"
1819 "git.secluded.site/lune/internal/validate"
1920 "github.com/charmbracelet/lipgloss"
......@@ -21,6 +22,11 @@ import (
2122 "github.com/spf13/cobra"
2223 )
2324
25+type enrichedTask struct {
26+ lunatask.Task
27+ Name string `json:"name,omitempty"`
28+}
29+
2430 // ErrUnknownArea indicates the specified area key was not found in config.
2531 var ErrUnknownArea = errors.New("unknown area key")
2632
......@@ -61,6 +67,16 @@ func runList(cmd *cobra.Command, _ []string) error {
6167 return err
6268 }
6369
70+ cfg, _ := config.Load()
71+ enriched := make([]enrichedTask, 0, len(tasks))
72+ for _, t := range tasks {
73+ name := ""
74+ if cfg != nil && cfg.Experimental.LocalDB {
75+ name, _ = db.EnrichTask(t.ID)
76+ }
77+ enriched = append(enriched, enrichedTask{Task: t, Name: name})
78+ }
79+
6480 areaID, err := resolveAreaFilter(cmd)
6581 if err != nil {
6682 return err
......@@ -72,19 +88,19 @@ func runList(cmd *cobra.Command, _ []string) error {
7288 }
7389
7490 showAll := mustGetBoolFlag(cmd, "all")
75- tasks = applyFilters(tasks, areaID, statusFilter, showAll)
91+ filtered := applyFiltersEnriched(enriched, areaID, statusFilter, showAll)
7692
77- if len(tasks) == 0 {
93+ if len(filtered) == 0 {
7894 fmt.Fprintln(cmd.OutOrStdout(), "No tasks found")
7995
8096 return nil
8197 }
8298
8399 if mustGetBoolFlag(cmd, "json") {
84- return outputJSON(cmd, tasks)
100+ return outputJSONEnriched(cmd, filtered)
85101 }
86102
87- return outputTable(cmd, tasks)
103+ return outputTableEnriched(cmd, filtered)
88104 }
89105
90106 // mustGetStringFlag returns the string flag value. Panics if flag doesn't exist
......@@ -156,25 +172,41 @@ func resolveStatusFilter(cmd *cobra.Command) (string, error) {
156172 return string(s), nil
157173 }
158174
159-func applyFilters(tasks []lunatask.Task, areaID, statusFilter string, showAll bool) []lunatask.Task {
175+func applyFiltersEnriched(tasks []enrichedTask, areaID, statusFilter string, showAll bool) []enrichedTask {
176+ // Extract basic tasks for library filter
177+ baseTasks := make([]lunatask.Task, len(tasks))
178+ for i, t := range tasks {
179+ baseTasks[i] = t.Task
180+ }
181+
160182 opts := &lunatask.TaskFilterOptions{
161183 IncludeCompleted: showAll,
162184 Today: time.Now(),
163185 }
164-
165186 if areaID != "" {
166187 opts.AreaID = &areaID
167188 }
168-
169189 if statusFilter != "" {
170190 s := lunatask.TaskStatus(statusFilter)
171191 opts.Status = &s
172192 }
173193
174- return lunatask.FilterTasks(tasks, opts)
194+ filteredBase := lunatask.FilterTasks(baseTasks, opts)
195+
196+ // Map back to enriched
197+ res := make([]enrichedTask, 0, len(filteredBase))
198+ for _, fb := range filteredBase {
199+ for _, et := range tasks {
200+ if et.ID == fb.ID {
201+ res = append(res, et)
202+ break
203+ }
204+ }
205+ }
206+ return res
175207 }
176208
177-func outputJSON(cmd *cobra.Command, tasks []lunatask.Task) error {
209+func outputJSONEnriched(cmd *cobra.Command, tasks []enrichedTask) error {
178210 enc := json.NewEncoder(cmd.OutOrStdout())
179211 enc.SetIndent("", " ")
180212
......@@ -185,7 +217,7 @@ func outputJSON(cmd *cobra.Command, tasks []lunatask.Task) error {
185217 return nil
186218 }
187219
188-func outputTable(cmd *cobra.Command, tasks []lunatask.Task) error {
220+func outputTableEnriched(cmd *cobra.Command, tasks []enrichedTask) error {
189221 rows := make([][]string, 0, len(tasks))
190222
191223 for _, task := range tasks {
......@@ -199,13 +231,16 @@ func outputTable(cmd *cobra.Command, tasks []lunatask.Task) error {
199231 scheduled = ui.FormatDate(task.ScheduledOn.Time)
200232 }
201233
202- created := ui.FormatDate(task.CreatedAt)
234+ name := task.Name
235+ if name == "" {
236+ name = task.ID[:8] + "..."
237+ }
203238
204- rows = append(rows, []string{task.ID, status, scheduled, created})
239+ rows = append(rows, []string{name, status, scheduled})
205240 }
206241
207242 tbl := table.New().
208- Headers("ID", "STATUS", "SCHEDULED", "CREATED").
243+ Headers("NAME", "STATUS", "SCHEDULED").
209244 Rows(rows...).
210245 StyleFunc(func(row, col int) lipgloss.Style {
211246 if row == table.HeaderRow {
+2 -0 go.mod #
......@@ -47,6 +47,7 @@ require (
4747 github.com/dustin/go-humanize v1.0.1 // indirect
4848 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
4949 github.com/godbus/dbus/v5 v5.2.2 // indirect
50+ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect
5051 github.com/google/uuid v1.6.0 // indirect
5152 github.com/inconshreveable/mousetrap v1.1.0 // indirect
5253 github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
......@@ -62,6 +63,7 @@ require (
6263 github.com/muesli/termenv v0.16.0 // indirect
6364 github.com/rivo/uniseg v0.4.7 // indirect
6465 github.com/spf13/pflag v1.0.10 // indirect
66+ github.com/syndtr/goleveldb v1.0.0 // indirect
6567 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
6668 github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
6769 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
7373 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
7474 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
7575 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
76+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
7677 github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
7778 github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
7879 github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
7980 github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
81+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
82+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
83+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
8084 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
8185 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
8286 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
8589 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
8690 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
8791 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
92+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
8893 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
8994 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
9095 github.com/klauspost/lctime v0.1.0 h1:nINsuFc860M9cyYhT6vfg6U1USh7kiVBj/s/2b04U70=
......@@ -115,6 +120,9 @@ github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
115120 github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
116121 github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
117122 github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
123+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
124+github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
125+github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
118126 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
119127 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
120128 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=
129137 github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
130138 github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
131139 github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
140+github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
141+github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
132142 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
133143 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
134144 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=
140150 golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
141151 golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
142152 golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
153+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
143154 golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
144155 golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
156+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
145157 golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
146158 golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
159+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
147160 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
148161 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
149162 golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
150163 golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
164+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
151165 golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
152166 golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
153167 golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
154168 golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
155169 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
170+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
171+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
172+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
156173 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
157174 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+59 -30 internal/client/client.go #
......@@ -6,9 +6,10 @@
66 package client
77
88 import (
9+ "encoding/json"
910 "errors"
10- "fmt"
1111 "os"
12+ "path/filepath"
1213 "runtime/debug"
1314
1415 "git.secluded.site/go-lunatask"
......@@ -39,58 +40,86 @@ func New() (*lunatask.Client, error) {
3940 }
4041
4142 // GetToken returns the access token from LUNE_ACCESS_TOKEN environment variable
42-// or keyring. Returns empty string and nil error if not found in either location;
43-// returns error for keyring access problems. Environment variable takes precedence.
43+// or keyring/secret store. Environment variable takes precedence.
4444 func GetToken() (string, error) {
45- // Env var takes precedence for explicit override
4645 if token := os.Getenv("LUNE_ACCESS_TOKEN"); token != "" {
4746 return token, nil
4847 }
4948
5049 token, err := keyring.Get(keyringService, keyringUser)
51- if err != nil {
52- if errors.Is(err, keyring.ErrNotFound) {
53- return "", nil
54- }
55-
56- return "", fmt.Errorf("accessing system keyring: %w", err)
50+ if err == nil {
51+ return token, nil
5752 }
5853
59- return token, nil
54+ // Keyring failed, try SecretStore
55+ store := NewSecretStore()
56+ return store.Get(keyringUser)
6057 }
6158
62-// SetToken stores the access token in the system keyring.
59+// SetToken stores the access token in the system keyring or SecretStore.
6360 func SetToken(token string) error {
64- if err := keyring.Set(keyringService, keyringUser, token); err != nil {
65- return fmt.Errorf("keyring set: %w", err)
61+ err := keyring.Set(keyringService, keyringUser, token)
62+ if err == nil {
63+ return nil
6664 }
6765
68- return nil
66+ // Keyring failed, save to SecretStore
67+ store := NewSecretStore()
68+ return store.Set(keyringUser, token)
6969 }
7070
71-// DeleteToken removes the access token from the system keyring.
72-func DeleteToken() error {
73- if err := keyring.Delete(keyringService, keyringUser); err != nil {
74- return fmt.Errorf("keyring delete: %w", err)
71+// SecretStore implementation moved to client for simplicity or kept in db package.
72+// For now, I'll add a minimal version here or import it correctly.
73+type SecretStore struct {
74+ Path string
75+}
76+
77+func NewSecretStore() *SecretStore {
78+ home, _ := os.UserHomeDir()
79+ return &SecretStore{
80+ Path: filepath.Join(home, ".config", "lune", "secrets.json"),
7581 }
82+}
7683
77- return nil
84+func (s *SecretStore) Set(key, value string) error {
85+ data := make(map[string]string)
86+ if f, err := os.ReadFile(s.Path); err == nil {
87+ json.Unmarshal(f, &data)
88+ }
89+ data[key] = value
90+ f, _ := json.Marshal(data)
91+ os.MkdirAll(filepath.Dir(s.Path), 0700)
92+ return os.WriteFile(s.Path, f, 0600)
7893 }
7994
80-// HasKeyringToken checks if an access token is stored in the keyring.
81-// Returns (true, nil) if found, (false, nil) if not found,
82-// or (false, error) if there was a keyring access problem.
83-func HasKeyringToken() (bool, error) {
84- _, err := keyring.Get(keyringService, keyringUser)
95+func (s *SecretStore) Get(key string) (string, error) {
96+ f, err := os.ReadFile(s.Path)
8597 if err != nil {
86- if errors.Is(err, keyring.ErrNotFound) {
87- return false, nil
88- }
98+ return "", nil
99+ }
100+ data := make(map[string]string)
101+ json.Unmarshal(f, &data)
102+ return data[key], nil
103+}
104+
105+// DeleteToken removes the access token from the system keyring and SecretStore.
106+func DeleteToken() error {
107+ keyring.Delete(keyringService, keyringUser)
108+ store := NewSecretStore()
109+ return store.Set(keyringUser, "")
110+}
89111
90- return false, fmt.Errorf("accessing system keyring: %w", err)
112+// HasKeyringToken checks if an access token is stored in the keyring or SecretStore.
113+func HasKeyringToken() (bool, error) {
114+ _, err := keyring.Get(keyringService, keyringUser)
115+ if err == nil {
116+ return true, nil
91117 }
92118
93- return true, nil
119+ // Keyring failed or not found, try SecretStore
120+ store := NewSecretStore()
121+ token, _ := store.Get(keyringUser)
122+ return token != "", nil
94123 }
95124
96125 // 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")
2020
2121 // Config represents the lune configuration file structure.
2222 type Config struct {
23- UI UIConfig `toml:"ui"`
24- Defaults Defaults `toml:"defaults"`
25- MCP MCPConfig `toml:"mcp"`
26- Areas []Area `toml:"areas"`
27- Notebooks []Notebook `toml:"notebooks"`
28- Habits []Habit `toml:"habits"`
23+ UI UIConfig `toml:"ui"`
24+ Defaults Defaults `toml:"defaults"`
25+ MCP MCPConfig `toml:"mcp"`
26+ Experimental ExperimentalConfig `toml:"experimental"`
27+ Areas []Area `toml:"areas"`
28+ Notebooks []Notebook `toml:"notebooks"`
29+ Habits []Habit `toml:"habits"`
30+}
31+
32+// ExperimentalConfig holds experimental features.
33+type ExperimentalConfig struct {
34+ LocalDB bool `toml:"local_db"`
35+}
36+
37+// ApplyDefaults enables all tools if none are explicitly configured.
38+func (c *Config) ApplyDefaults() {
39+ c.MCP.MCPDefaults()
40+ // Other defaults can go here
2941 }
3042
3143 // MCPConfig holds MCP server settings.
Back to top