Skip to content

Commit b11c06a

Browse files
committed
Add REST server functionality and commands
- Introduced REST server commands: start, stop, status, and help. - Added configuration options for enabling/disabling the server and setting the port. - Implemented server command handling in the executor. - Created a daemon for running the server in the background. - Developed REST API endpoints for executing commands and checking server status. - Enhanced documentation with examples for using the REST API in Bash, Python, JavaScript, and HTML. - Updated configuration structure to include server settings.
1 parent 7a4e7aa commit b11c06a

9 files changed

Lines changed: 1180 additions & 140 deletions

File tree

cmd/lumo/main.go

Lines changed: 136 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@ package main
22

33
import (
44
"fmt"
5+
"log"
56
"os"
7+
"os/signal"
68
"strings"
9+
"syscall"
710
"time"
811

912
"github.com/agnath18K/lumo/pkg/agent"
1013
"github.com/agnath18K/lumo/pkg/config"
14+
"github.com/agnath18K/lumo/pkg/daemon"
1115
"github.com/agnath18K/lumo/pkg/executor"
1216
"github.com/agnath18K/lumo/pkg/nlp"
1317
"github.com/agnath18K/lumo/pkg/pipe"
18+
"github.com/agnath18K/lumo/pkg/server"
1419
"github.com/agnath18K/lumo/pkg/terminal"
1520
"github.com/agnath18K/lumo/pkg/utils"
1621
"github.com/agnath18K/lumo/pkg/version"
@@ -32,6 +37,78 @@ func main() {
3237
// Initialize agent
3338
_ = agent.Initialize(cfg, exec)
3439

40+
// Check for server daemon commands
41+
if len(os.Args) > 1 {
42+
// Handle server daemon commands
43+
if os.Args[1] == "server:start" {
44+
// Start the server daemon
45+
d := daemon.New(cfg)
46+
if err := d.Start(); err != nil {
47+
fmt.Fprintf(os.Stderr, "Error starting server daemon: %v\n", err)
48+
os.Exit(1)
49+
}
50+
fmt.Println("Server daemon started")
51+
os.Exit(0)
52+
} else if os.Args[1] == "server:stop" {
53+
// Stop the server daemon
54+
d := daemon.New(cfg)
55+
if err := d.Stop(); err != nil {
56+
fmt.Fprintf(os.Stderr, "Error stopping server daemon: %v\n", err)
57+
os.Exit(1)
58+
}
59+
fmt.Println("Server daemon stopped")
60+
os.Exit(0)
61+
} else if os.Args[1] == "server:status" {
62+
// Check server daemon status
63+
d := daemon.New(cfg)
64+
running, pid, err := d.Status()
65+
if err != nil {
66+
fmt.Fprintf(os.Stderr, "Error checking server daemon status: %v\n", err)
67+
os.Exit(1)
68+
}
69+
if running {
70+
fmt.Printf("Server daemon is running with PID %d\n", pid)
71+
} else {
72+
fmt.Println("Server daemon is not running")
73+
}
74+
os.Exit(0)
75+
} else if os.Args[1] == "server:daemon" {
76+
// This is the daemon process
77+
d := daemon.New(cfg)
78+
if err := d.RunServer(exec); err != nil {
79+
fmt.Fprintf(os.Stderr, "Error running server daemon: %v\n", err)
80+
os.Exit(1)
81+
}
82+
os.Exit(0)
83+
}
84+
}
85+
86+
// Check if a server daemon is already running
87+
d := daemon.New(cfg)
88+
running, _, err := d.IsRunning()
89+
if err != nil {
90+
fmt.Fprintf(os.Stderr, "Error checking if server daemon is running: %v\n", err)
91+
}
92+
93+
// Start the REST server if enabled and not already running as a daemon
94+
var srv *server.Server
95+
if cfg.EnableServer && !running {
96+
srv = server.New(cfg, exec)
97+
if err := srv.Start(); err != nil {
98+
fmt.Fprintf(os.Stderr, "Error starting REST server: %v\n", err)
99+
// Continue execution even if server fails to start
100+
} else {
101+
// Set up signal handling for graceful shutdown
102+
setupSignalHandling(srv)
103+
104+
// Notify the user that the server is running
105+
if !cfg.ServerQuietOutput {
106+
fmt.Fprintf(os.Stderr, "\nNOTE: Lumo REST server is running on port %d\n", cfg.ServerPort)
107+
fmt.Fprintf(os.Stderr, "To disable the server, run: lumo config:server disable\n\n")
108+
}
109+
}
110+
}
111+
35112
// Check if input is being piped
36113
stat, _ := os.Stdin.Stat()
37114
isPiped := (stat.Mode() & os.ModeCharDevice) == 0
@@ -80,7 +157,7 @@ func main() {
80157
hasPrefix := false
81158
for _, prefix := range []string{"lumo:", "shell:", "ask:", "ai:", "auto:", "agent:",
82159
"health:", "syshealth:", "report:", "sysreport:", "chat:", "talk:", "config:",
83-
"speed:", "speedtest:", "speed-test:", "magic:", "clipboard", "connect", "create"} {
160+
"speed:", "speedtest:", "speed-test:", "magic:", "clipboard", "connect", "create", "server:"} {
84161
if strings.HasPrefix(command, prefix) {
85162
hasPrefix = true
86163
break
@@ -121,6 +198,43 @@ func main() {
121198
os.Exit(1)
122199
}
123200
term.Display(result)
201+
} else if strings.HasPrefix(command, "server:") {
202+
// Handle server commands
203+
intent := strings.TrimSpace(command[7:])
204+
if intent == "start" {
205+
// Start the server daemon
206+
d := daemon.New(cfg)
207+
if err := d.Start(); err != nil {
208+
fmt.Fprintf(os.Stderr, "Error starting server daemon: %v\n", err)
209+
os.Exit(1)
210+
}
211+
fmt.Println("Server daemon started")
212+
} else if intent == "stop" {
213+
// Stop the server daemon
214+
d := daemon.New(cfg)
215+
if err := d.Stop(); err != nil {
216+
fmt.Fprintf(os.Stderr, "Error stopping server daemon: %v\n", err)
217+
os.Exit(1)
218+
}
219+
fmt.Println("Server daemon stopped")
220+
} else if intent == "status" {
221+
// Check server daemon status
222+
d := daemon.New(cfg)
223+
running, pid, err := d.Status()
224+
if err != nil {
225+
fmt.Fprintf(os.Stderr, "Error checking server daemon status: %v\n", err)
226+
os.Exit(1)
227+
}
228+
if running {
229+
fmt.Printf("Server daemon is running with PID %d\n", pid)
230+
} else {
231+
fmt.Println("Server daemon is not running")
232+
}
233+
} else {
234+
fmt.Fprintf(os.Stderr, "Unknown server command: %s\n", intent)
235+
fmt.Println("Available commands: server:start, server:stop, server:status")
236+
os.Exit(1)
237+
}
124238
} else if strings.HasPrefix(command, "lumo:") {
125239
// Legacy "lumo:" prefix is now treated as an AI query for safety
126240
intent := strings.TrimSpace(command[5:])
@@ -136,144 +250,6 @@ func main() {
136250
os.Exit(1)
137251
}
138252
term.Display(result)
139-
} else if strings.HasPrefix(command, "health:") || strings.HasPrefix(command, "syshealth:") {
140-
// Handle system health commands
141-
var intent string
142-
if strings.HasPrefix(command, "health:") {
143-
intent = strings.TrimSpace(command[7:])
144-
} else {
145-
intent = strings.TrimSpace(command[10:])
146-
}
147-
cmd := &nlp.Command{
148-
Type: nlp.CommandTypeSystemHealth,
149-
Intent: intent,
150-
Parameters: make(map[string]string),
151-
RawInput: command,
152-
}
153-
result, err := exec.Execute(cmd)
154-
if err != nil {
155-
fmt.Fprintf(os.Stderr, "Error executing command: %v\n", err)
156-
os.Exit(1)
157-
}
158-
term.Display(result)
159-
} else if strings.HasPrefix(command, "report:") || strings.HasPrefix(command, "sysreport:") {
160-
// Handle system report commands
161-
var intent string
162-
if strings.HasPrefix(command, "report:") {
163-
intent = strings.TrimSpace(command[7:])
164-
} else {
165-
intent = strings.TrimSpace(command[10:])
166-
}
167-
cmd := &nlp.Command{
168-
Type: nlp.CommandTypeSystemReport,
169-
Intent: intent,
170-
Parameters: make(map[string]string),
171-
RawInput: command,
172-
}
173-
result, err := exec.Execute(cmd)
174-
if err != nil {
175-
fmt.Fprintf(os.Stderr, "Error executing command: %v\n", err)
176-
os.Exit(1)
177-
}
178-
term.Display(result)
179-
} else if strings.HasPrefix(command, "chat:") || strings.HasPrefix(command, "talk:") || command == "chat" || command == "talk" {
180-
// Handle chat commands
181-
var intent string
182-
var rawInput string
183-
184-
if command == "chat" || command == "talk" {
185-
// Empty chat command to start REPL mode
186-
intent = ""
187-
rawInput = command + ":"
188-
} else if strings.HasPrefix(command, "chat:") {
189-
intent = strings.TrimSpace(command[5:])
190-
rawInput = command
191-
} else {
192-
intent = strings.TrimSpace(command[5:])
193-
rawInput = command
194-
}
195-
196-
cmd := &nlp.Command{
197-
Type: nlp.CommandTypeChat,
198-
Intent: intent,
199-
Parameters: make(map[string]string),
200-
RawInput: rawInput,
201-
}
202-
result, err := exec.Execute(cmd)
203-
if err != nil {
204-
fmt.Fprintf(os.Stderr, "Error executing command: %v\n", err)
205-
os.Exit(1)
206-
}
207-
term.Display(result)
208-
} else if strings.HasPrefix(command, "config:") {
209-
// Handle configuration commands
210-
intent := strings.TrimSpace(command[7:])
211-
cmd := &nlp.Command{
212-
Type: nlp.CommandTypeConfig,
213-
Intent: intent,
214-
Parameters: make(map[string]string),
215-
RawInput: command,
216-
}
217-
result, err := exec.Execute(cmd)
218-
if err != nil {
219-
fmt.Fprintf(os.Stderr, "Error executing command: %v\n", err)
220-
os.Exit(1)
221-
}
222-
term.Display(result)
223-
} else if strings.HasPrefix(command, "magic:") {
224-
// Handle magic commands
225-
intent := strings.TrimSpace(command[6:])
226-
cmd := &nlp.Command{
227-
Type: nlp.CommandTypeMagic,
228-
Intent: intent,
229-
Parameters: make(map[string]string),
230-
RawInput: command,
231-
}
232-
result, err := exec.Execute(cmd)
233-
if err != nil {
234-
fmt.Fprintf(os.Stderr, "Error executing command: %v\n", err)
235-
os.Exit(1)
236-
}
237-
term.Display(result)
238-
} else if command == "clipboard" || strings.HasPrefix(command, "clipboard ") {
239-
// Handle clipboard commands
240-
intent := ""
241-
if strings.HasPrefix(command, "clipboard ") {
242-
intent = strings.TrimSpace(command[10:])
243-
}
244-
cmd := &nlp.Command{
245-
Type: nlp.CommandTypeClipboard,
246-
Intent: intent,
247-
Parameters: make(map[string]string),
248-
RawInput: command,
249-
}
250-
result, err := exec.Execute(cmd)
251-
if err != nil {
252-
fmt.Fprintf(os.Stderr, "Error executing command: %v\n", err)
253-
os.Exit(1)
254-
}
255-
term.Display(result)
256-
} else if strings.HasPrefix(command, "create:") || command == "create" {
257-
// Handle create commands
258-
var intent string
259-
if strings.HasPrefix(command, "create:") {
260-
intent = strings.TrimSpace(command[7:])
261-
} else {
262-
// Just "create" shows help
263-
intent = ""
264-
}
265-
cmd := &nlp.Command{
266-
Type: nlp.CommandTypeCreate,
267-
Intent: intent,
268-
Parameters: make(map[string]string),
269-
RawInput: command,
270-
}
271-
result, err := exec.Execute(cmd)
272-
if err != nil {
273-
fmt.Fprintf(os.Stderr, "Error executing command: %v\n", err)
274-
os.Exit(1)
275-
}
276-
term.Display(result)
277253
} else {
278254
processCommand(command, parser, exec, term)
279255
}
@@ -288,6 +264,27 @@ func main() {
288264
}
289265
}
290266

267+
// setupSignalHandling sets up signal handling for graceful shutdown
268+
func setupSignalHandling(srv *server.Server) {
269+
c := make(chan os.Signal, 1)
270+
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
271+
272+
go func() {
273+
<-c
274+
if !srv.GetConfig().ServerQuietOutput {
275+
log.Println("Shutting down REST server...")
276+
}
277+
if err := srv.Stop(); err != nil {
278+
if !srv.GetConfig().ServerQuietOutput {
279+
log.Printf("Error stopping server: %v", err)
280+
}
281+
}
282+
if !srv.GetConfig().ServerQuietOutput {
283+
log.Println("Server stopped")
284+
}
285+
}()
286+
}
287+
291288
func processPipedInput(exec *executor.Executor, term *terminal.Terminal) {
292289
// Record start time for performance measurement
293290
startTime := time.Now()

0 commit comments

Comments
 (0)