snapshot of c000b74a5b09c433f96a69fa102c5c8f6583dff0 Annotations about the code that implements koment.

internal/cli/cli.go

1 // Package cli implements the koment commands a human or a shell hook runs.
2 package cli
3
4 import (
5 "errors"
6 "flag"
7 "fmt"
8 "io"
9 "os"
10 "strings"
11
12 "github.com/koment-dev/koment/internal/application"
13 "github.com/koment-dev/koment/internal/policy"
14 repositorymodel "github.com/koment-dev/koment/internal/repository"
15 "github.com/koment-dev/koment/internal/store"
16 )
17
18 const (
19 ExitOK = 0
20 ExitFailure = 1
21 ExitUsage = 2
22 )
23
24 type Environment struct {
25 Stdin io.Reader
26 Stdout io.Writer
27 Stderr io.Writer
28 Build Build
29 }
30
31 // Server runs a long-lived server, parsing its own flags.
32 type Server func(args []string, stderr io.Writer) error
33
34 // Servers are injected rather than imported. Adding one is a new field, not a
35 // new parameter, so signatures here stop changing shape.
36 type Servers struct {
37 MCP Server
38 UI Server
39 Site Server
40 Serve Server
41 LSP Server
42 }
43
44 // Run dispatches a subcommand.
45 func Run(args []string, env Environment, servers Servers) int {
46 if len(args) == 0 {
47 writeUsage(env.Stderr)
48 return ExitUsage
49 }
50
51 command, rest := args[0], args[1:]
52 run, known := map[string]func([]string, Environment) int{
53 "add": runAdd,
54 "agents": runAgents,
55 "bootstrap": runBootstrap,
56 "show": runShow,
57 "check": runCheck,
58 "comments": runComments,
59 "edit": runEdit,
60 "forget": runForget,
61 "list": runList,
62 "search": runSearch,
63 "reanchor": runReanchor,
64 "version": runVersion,
65 }[command]
66
67 switch {
68 case known:
69 return run(rest, env)
70 case command == "mcp":
71 if err := servers.MCP(rest, env.Stderr); err != nil {
72 return fail(env, err)
73 }
74 return ExitOK
75 case command == "ui":
76 if err := servers.UI(rest, env.Stderr); err != nil {
77 return fail(env, err)
78 }
79 return ExitOK
80 case command == "site":
81 if err := servers.Site(rest, env.Stderr); err != nil {
82 return fail(env, err)
83 }
84 return ExitOK
85 case command == "serve":
86 if err := servers.Serve(rest, env.Stderr); err != nil {
87 return fail(env, err)
88 }
89 return ExitOK
90 case command == "lsp":
91 if err := servers.LSP(rest, env.Stderr); err != nil {
92 return fail(env, err)
93 }
94 return ExitOK
95 case command == "help", command == "-h", command == "--help":
96 writeUsage(env.Stdout)
97 return ExitOK
98 }
99
100 fmt.Fprintf(env.Stderr, "koment: unknown command %q\n\n", command)
101 writeUsage(env.Stderr)
102 return ExitUsage
103 }
104
105 func fail(env Environment, err error) int {
106 fmt.Fprintf(env.Stderr, "koment: %v\n", err)
107 return ExitFailure
108 }
109
110 func misuse(env Environment, format string, args ...any) int {
111 fmt.Fprintf(env.Stderr, "koment: "+format+"\n", args...)
112 return ExitUsage
113 }
114
115 func flagSet(name string, env Environment) *flag.FlagSet {
116 flags := flag.NewFlagSet(name, flag.ContinueOnError)
117 flags.SetOutput(env.Stderr)
118 flags.Usage = func() { writeCommandUsage(env.Stderr, name, flags) }
119 return flags
120 }
121
122 func parse(flags *flag.FlagSet, args []string) (int, bool) {
123 err := flags.Parse(args)
124 switch {
125 case err == nil:
126 return ExitOK, true
127 case errors.Is(err, flag.ErrHelp):
128 return ExitOK, false
129 default:
130 return ExitUsage, false
131 }
132 }
133
134 func onePositional(command, what string, flags *flag.FlagSet, args []string, env Environment) (string, int, bool) {
135 value, rest := leadingNonFlag(args)
136 if code, ok := parse(flags, rest); !ok {
137 return "", code, false
138 }
139
140 switch {
141 case value == "":
142 value = flags.Arg(0)
143 case flags.NArg() > 0:
144 misuse(env, "%s takes one %s, also got %s", command, what, strings.Join(flags.Args(), " "))
145 return "", ExitUsage, false
146 }
147
148 if value == "" {
149 misuse(env, "%s needs %s", command, what)
150 return "", ExitUsage, false
151 }
152 return value, ExitOK, true
153 }
154
155 func leadingNonFlag(args []string) (string, []string) {
156 if len(args) > 0 && !strings.HasPrefix(args[0], "-") {
157 return args[0], args[1:]
158 }
159 return "", args
160 }
161
162 func openStore() (*store.Store, error) {
163 workingDirectory, err := os.Getwd()
164 if err != nil {
165 return nil, fmt.Errorf("finding the working directory: %w", err)
166 }
167 root, err := store.FindRoot(workingDirectory)
168 if err != nil {
169 return nil, err
170 }
171 return store.Open(root), nil
172 }
173
174 func openApplication() (*application.Service, *store.Store, error) {
175 annotations, err := openStore()
176 if err != nil {
177 return nil, nil, err
178 }
179 entry := repositorymodel.Repository{ID: "local", Name: "Local repository", Root: annotations.Root()}
180 return application.NewService(entry), annotations, nil
181 }
182
183 type activeRepository struct {
184 service *application.Service
185 annotations *store.Store
186 configured policy.Policy
187 }
188
189 func openActiveRepository() (*activeRepository, error) {
190 workingDirectory, err := os.Getwd()
191 if err != nil {
192 return nil, fmt.Errorf("finding the working directory: %w", err)
193 }
194 activation, err := policy.Detect(workingDirectory)
195 if err != nil {
196 return nil, err
197 }
198 if activation == nil {
199 return nil, nil
200 }
201 annotations := store.Open(activation.Root)
202 entry := repositorymodel.Repository{ID: "local", Name: "Local repository", Root: activation.Root}
203 return &activeRepository{
204 service: application.NewService(entry),
205 annotations: annotations,
206 configured: activation.Configured,
207 }, nil
208 }

Find an annotation

Search file paths, rationale, kinds, and authors.

moveEnter openEsc close