@@ -211,9 +211,56 @@ func (s *Service) startWithContent(content, ruleSetProxy string) error {
211211 }
212212 slog .Debug ("[startWithContent] config translated" , "jsonBytes" , len (jsonContent ))
213213
214+ jsonContent = ensureClashModes (jsonContent )
214215 return s .startWithJSON (jsonContent , & libbox.OverrideOptions {}, false , nil , nil )
215216}
216217
218+ // ensureClashModes injects harmless route rules that reference all three base
219+ // clash modes so that sing-box's CalculateClashModeList always discovers them.
220+ // The rules use .invalid domains that never match real traffic.
221+ func ensureClashModes (jsonContent string ) string {
222+ var top map [string ]json.RawMessage
223+ if err := json .Unmarshal ([]byte (jsonContent ), & top ); err != nil {
224+ return jsonContent
225+ }
226+ routeRaw , ok := top ["route" ]
227+ if ! ok {
228+ return jsonContent
229+ }
230+ var route map [string ]json.RawMessage
231+ if err := json .Unmarshal (routeRaw , & route ); err != nil {
232+ return jsonContent
233+ }
234+
235+ type modeRule struct {
236+ Domain []string `json:"domain"`
237+ ClashMode string `json:"clash_mode"`
238+ Outbound string `json:"outbound"`
239+ }
240+ indicators := []modeRule {
241+ {Domain : []string {"mode-rule.invalid" }, ClashMode : "Rule" , Outbound : "direct" },
242+ {Domain : []string {"mode-direct.invalid" }, ClashMode : "Direct" , Outbound : "direct" },
243+ {Domain : []string {"mode-global.invalid" }, ClashMode : "Global" , Outbound : "direct" },
244+ }
245+
246+ var rules []json.RawMessage
247+ if raw , ok := route ["rules" ]; ok {
248+ _ = json .Unmarshal (raw , & rules )
249+ }
250+ for _ , r := range indicators {
251+ b , _ := json .Marshal (r )
252+ rules = append (rules , b )
253+ }
254+
255+ rulesJSON , _ := json .Marshal (rules )
256+ route ["rules" ] = json .RawMessage (rulesJSON )
257+ routeJSON , _ := json .Marshal (route )
258+ top ["route" ] = json .RawMessage (routeJSON )
259+
260+ out , _ := json .Marshal (top )
261+ return string (out )
262+ }
263+
217264func parseOverrideOptions (jsonStr string ) (* libbox.OverrideOptions , OverrideConfig , error ) {
218265 if jsonStr == "" {
219266 return & libbox.OverrideOptions {}, OverrideConfig {}, nil
@@ -290,6 +337,7 @@ func (s *Service) startWithJSON(jsonContent string, override *libbox.OverrideOpt
290337 opts .AddCommand (libbox .CommandStatus )
291338 opts .AddCommand (libbox .CommandGroup )
292339 opts .AddCommand (libbox .CommandConnections )
340+ opts .AddCommand (libbox .CommandClashMode )
293341
294342 newClient := libbox .NewCommandClient (s .handler , opts )
295343 slog .Debug ("[startWithJSON] calling CommandClient.Connect" )
0 commit comments