1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5// Package client provides a configured Lunatask API client.
6package client
7
8import (
9 "errors"
10 "fmt"
11 "os"
12 "runtime/debug"
13
14 "git.secluded.site/go-lunatask"
15 "github.com/zalando/go-keyring"
16)
17
18const (
19 keyringService = "lune"
20 keyringUser = "api-key"
21)
22
23// ErrNoToken indicates no access token is available.
24var ErrNoToken = errors.New("no access token found; set LUNE_ACCESS_TOKEN or run 'lune init' to configure")
25
26// New creates a Lunatask client using the access token from LUNE_ACCESS_TOKEN
27// environment variable or system keyring. Environment variable takes precedence.
28func New() (*lunatask.Client, error) {
29 token, err := GetToken()
30 if err != nil {
31 return nil, err
32 }
33
34 if token == "" {
35 return nil, ErrNoToken
36 }
37
38 return lunatask.NewClient(token, lunatask.UserAgent("lune/"+version())), nil
39}
40
41// 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.
44func GetToken() (string, error) {
45 // Env var takes precedence for explicit override
46 if token := os.Getenv("LUNE_ACCESS_TOKEN"); token != "" {
47 return token, nil
48 }
49
50 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)
57 }
58
59 return token, nil
60}
61
62// SetToken stores the access token in the system keyring.
63func SetToken(token string) error {
64 if err := keyring.Set(keyringService, keyringUser, token); err != nil {
65 return fmt.Errorf("keyring set: %w", err)
66 }
67
68 return nil
69}
70
71// DeleteToken removes the access token from the system keyring.
72func DeleteToken() error {
73 if err := keyring.Delete(keyringService, keyringUser); err != nil {
74 return fmt.Errorf("keyring delete: %w", err)
75 }
76
77 return nil
78}
79
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.
83func HasKeyringToken() (bool, error) {
84 _, err := keyring.Get(keyringService, keyringUser)
85 if err != nil {
86 if errors.Is(err, keyring.ErrNotFound) {
87 return false, nil
88 }
89
90 return false, fmt.Errorf("accessing system keyring: %w", err)
91 }
92
93 return true, nil
94}
95
96// version returns the module version from build info, or "dev" if unavailable.
97func version() string {
98 info, ok := debug.ReadBuildInfo()
99 if !ok || info.Main.Version == "" || info.Main.Version == "(devel)" {
100 return "dev"
101 }
102
103 return info.Main.Version
104}