diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..cdc283d0a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,35 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Default settings for all files +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# Go files +[*.go] +indent_style = tab +indent_size = 4 + +# JSON, YAML, and Markdown files +[*.{json,yml,yaml,md}] +indent_style = space +indent_size = 2 + +# JavaScript/TypeScript files +[*.{js,ts,tsx}] +indent_style = space +indent_size = 2 + +# Shell scripts +[*.sh] +indent_style = space +indent_size = 2 + +# Makefile +[Makefile] +indent_style = tab diff --git a/.gitignore b/.gitignore index 135867842..4b35ebe77 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,24 @@ web/backend/dist/* docker/data .omc/ + +# Temporary files +*.tmp +*.temp +*.log + +# Local development +local/ +local.* + +# Node modules for frontend +web/frontend/node_modules/ +web/frontend/dist/ +web/frontend/.cache/ + +# Python virtual environments +venv/ +env/ +ENV/ +__pycache__/ +*.py[cod] diff --git a/README.md b/README.md index 1ab514a29..2fd285539 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@
-
+
diff --git a/cmd/picoclaw/internal/gateway/command.go b/cmd/picoclaw/internal/gateway/command.go
index 7fa588c5c..abc17a2ae 100644
--- a/cmd/picoclaw/internal/gateway/command.go
+++ b/cmd/picoclaw/internal/gateway/command.go
@@ -2,12 +2,14 @@ package gateway
import (
"fmt"
+ "os"
"github.com/spf13/cobra"
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
"github.com/sipeed/picoclaw/pkg/gateway"
"github.com/sipeed/picoclaw/pkg/logger"
+ "github.com/sipeed/picoclaw/pkg/pid"
"github.com/sipeed/picoclaw/pkg/utils"
)
@@ -48,5 +50,42 @@ func NewGatewayCommand() *cobra.Command {
"Continue starting even when no default model is configured",
)
+ // Add stop subcommand
+ cmd.AddCommand(newGatewayStopCommand())
+
return cmd
}
+
+func newGatewayStopCommand() *cobra.Command {
+ return &cobra.Command{
+ Use: "stop",
+ Short: "Stop the running picoclaw gateway",
+ Long: "Stop the running picoclaw gateway by reading its PID file and sending a termination signal.",
+ Args: cobra.NoArgs,
+ RunE: func(_ *cobra.Command, _ []string) error {
+ homePath := internal.GetPicoclawHome()
+
+ // Read PID file and check if process is running
+ data := pid.ReadPidFileWithCheck(homePath)
+ if data == nil {
+ fmt.Println("gateway is not running")
+ return nil
+ }
+
+ // Find the process
+ process, err := os.FindProcess(data.PID)
+ if err != nil {
+ return fmt.Errorf("failed to find gateway process (PID: %d): %w", data.PID, err)
+ }
+
+ // Send termination signal
+ fmt.Printf("stopping gateway (PID: %d)...\n", data.PID)
+ if err := stopProcess(process); err != nil {
+ return fmt.Errorf("failed to stop gateway (PID: %d): %w", data.PID, err)
+ }
+
+ fmt.Println("gateway stopped successfully")
+ return nil
+ },
+ }
+}
diff --git a/cmd/picoclaw/internal/gateway/stop_unix.go b/cmd/picoclaw/internal/gateway/stop_unix.go
new file mode 100644
index 000000000..e4a5052f9
--- /dev/null
+++ b/cmd/picoclaw/internal/gateway/stop_unix.go
@@ -0,0 +1,13 @@
+//go:build !windows
+
+package gateway
+
+import (
+ "os"
+ "syscall"
+)
+
+// stopProcess sends SIGTERM to the process for graceful shutdown on Unix-like systems.
+func stopProcess(process *os.Process) error {
+ return process.Signal(syscall.SIGTERM)
+}
diff --git a/cmd/picoclaw/internal/gateway/stop_windows.go b/cmd/picoclaw/internal/gateway/stop_windows.go
new file mode 100644
index 000000000..99b3065d1
--- /dev/null
+++ b/cmd/picoclaw/internal/gateway/stop_windows.go
@@ -0,0 +1,12 @@
+//go:build windows
+
+package gateway
+
+import (
+ "os"
+)
+
+// stopProcess kills the process on Windows (SIGTERM is not supported).
+func stopProcess(process *os.Process) error {
+ return process.Kill()
+}
diff --git a/pkg/channels/base.go b/pkg/channels/base.go
index 3585fb075..97c14332c 100644
--- a/pkg/channels/base.go
+++ b/pkg/channels/base.go
@@ -5,6 +5,7 @@ import (
"crypto/rand"
"encoding/binary"
"encoding/hex"
+ "fmt"
"regexp"
"strconv"
"strings"
@@ -128,9 +129,16 @@ func NewBaseChannel(
// currently defaults to accepting messages from ANYONE. To explicitly
// acknowledge and permit this (e.g. for a public bot), use ["*"].
if len(bc.allowList) == 0 {
- logger.WarnCF("channels", "SECURITY: Channel allows EVERYONE (allow_from is empty)", map[string]any{
- "channel": bc.name,
- "hint": "Set allow_from to your ID, or use '*' to explicitly acknowledge open access.",
+ logger.WarnCF("channels", fmt.Sprintf("SECURITY: Channel '%s' allows EVERYONE (allow_from is empty). This is a potential security risk.", bc.name), map[string]any{
+ "channel": bc.name,
+ "channelID": bc.name,
+ "hint": "Set allow_from to your ID, or use ['*'] to explicitly acknowledge open access. See: https://github.com/sipeed/picoclaw/blob/main/docs/configuration.md",
+ })
+ } else {
+ logger.InfoCF("channels", fmt.Sprintf("Channel '%s' allow_from configured (%d entries)", bc.name, len(bc.allowList)), map[string]any{
+ "channel": bc.name,
+ "allow_list": bc.allowList,
+ "count": len(bc.allowList),
})
}