feat: add agent-friendly ufile CLI

This commit is contained in:
loveuer
2026-09-21 00:45:35 -07:00
parent f0c29e4832
commit a58990ce66
17 changed files with 1684 additions and 3 deletions
+123
View File
@@ -0,0 +1,123 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
)
type commandError struct {
Code string
Message string
HTTPStatus int
ExitCode int
Cause error
}
func (e *commandError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("%s: %v", e.Message, e.Cause)
}
return e.Message
}
func usageError(message string) *commandError {
return &commandError{Code: "usage_error", Message: message, ExitCode: exitUsage}
}
func localError(message string, cause error) *commandError {
return &commandError{Code: "local_io_error", Message: message, ExitCode: exitLocalIO, Cause: cause}
}
func networkError(message string, cause error) *commandError {
return &commandError{Code: "network_error", Message: message, ExitCode: exitNetwork, Cause: cause}
}
func fromHTTPError(err *apiError) *commandError {
code := err.Code
exitCode := exitGeneral
switch err.Status {
case http.StatusBadRequest, http.StatusConflict, http.StatusUnprocessableEntity:
exitCode = exitValidation
if code == "" {
code = "validation_failed"
}
case http.StatusUnauthorized:
exitCode = exitAuth
if code == "" {
code = "authentication_required"
}
case http.StatusForbidden:
exitCode = exitPermission
if code == "" {
code = "permission_denied"
}
case http.StatusNotFound, http.StatusGone:
exitCode = exitUnavailable
if code == "" {
code = "file_unavailable"
}
default:
if err.Status >= 500 {
exitCode = exitNetwork
}
}
if code == "" {
code = "request_failed"
}
return &commandError{Code: code, Message: err.Message, HTTPStatus: err.Status, ExitCode: exitCode}
}
func classifyError(err error) *commandError {
if err == nil {
return nil
}
var commandErr *commandError
if errors.As(err, &commandErr) {
return commandErr
}
var remoteErr *apiError
if errors.As(err, &remoteErr) {
return fromHTTPError(remoteErr)
}
var netErr net.Error
if errors.As(err, &netErr) {
return networkError("请求 ufile 服务失败", err)
}
return &commandError{Code: "unexpected_error", Message: "执行失败", ExitCode: exitGeneral, Cause: err}
}
func writeCommandError(out io.Writer, err *commandError) int {
payload := struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
HTTPStatus int `json:"httpStatus,omitempty"`
} `json:"error"`
}{}
payload.Error.Code = err.Code
payload.Error.Message = err.Message
if err.Cause != nil {
payload.Error.Message += ": " + err.Cause.Error()
}
payload.Error.HTTPStatus = err.HTTPStatus
encoder := json.NewEncoder(out)
encoder.SetEscapeHTML(false)
_ = encoder.Encode(payload)
if err.ExitCode == 0 {
return exitGeneral
}
return err.ExitCode
}
func writeJSON(out io.Writer, value any) int {
encoder := json.NewEncoder(out)
encoder.SetEscapeHTML(false)
if err := encoder.Encode(value); err != nil {
return exitLocalIO
}
return exitOK
}