44 "context"
55 "errors"
66 "fmt"
7+ "net"
8+ neturl "net/url"
79 "os"
810 "strings"
911
1719 runDaemonInstallCommand = defaultDaemonInstallCommandRunner
1820 runDaemonUninstallCommand = defaultDaemonUninstallCommandRunner
1921 runDaemonStatusCommand = defaultDaemonStatusCommandRunner
22+ runDaemonEncodeCommand = defaultDaemonEncodeCommandRunner
2023
2124 serveHTTPDaemon = urlscheme .ServeHTTPDaemon
2225 installHTTPDaemon = urlscheme .InstallHTTPDaemon
@@ -38,6 +41,15 @@ type daemonStatusCommandOptions struct {
3841 ListenAddress string
3942}
4043
44+ type daemonEncodeCommandOptions struct {
45+ Action string
46+ Prompt string
47+ Path string
48+ Workdir string
49+ SessionID string
50+ ListenAddress string
51+ }
52+
4153// newDaemonCommand 创建 daemon 命令组,承载 HTTP 唤醒服务与自启动管理能力。
4254func newDaemonCommand () * cobra.Command {
4355 command := & cobra.Command {
@@ -55,6 +67,7 @@ func newDaemonCommand() *cobra.Command {
5567 newDaemonInstallCommand (),
5668 newDaemonUninstallCommand (),
5769 newDaemonStatusCommand (),
70+ newDaemonEncodeCommand (),
5871 )
5972 return command
6073}
@@ -159,6 +172,84 @@ func newDaemonStatusCommand() *cobra.Command {
159172 return command
160173}
161174
175+ // newDaemonEncodeCommand 创建 daemon encode 子命令组。
176+ func newDaemonEncodeCommand () * cobra.Command {
177+ command := & cobra.Command {
178+ Use : "encode" ,
179+ Short : "Generate clickable HTTP wake URL" ,
180+ SilenceUsage : true ,
181+ SilenceErrors : true ,
182+ Args : cobra .NoArgs ,
183+ }
184+ command .AddCommand (
185+ newDaemonEncodeRunCommand (),
186+ newDaemonEncodeReviewCommand (),
187+ )
188+ return command
189+ }
190+
191+ // newDaemonEncodeRunCommand 创建 daemon encode run 子命令。
192+ func newDaemonEncodeRunCommand () * cobra.Command {
193+ options := & daemonEncodeCommandOptions {Action : "run" }
194+ command := & cobra.Command {
195+ Use : "run" ,
196+ Short : "Encode wake run URL" ,
197+ SilenceUsage : true ,
198+ SilenceErrors : true ,
199+ Args : cobra .NoArgs ,
200+ RunE : func (cmd * cobra.Command , args []string ) error {
201+ return runDaemonEncodeCommand (cmd .Context (), daemonEncodeCommandOptions {
202+ Action : "run" ,
203+ Prompt : strings .TrimSpace (options .Prompt ),
204+ Workdir : strings .TrimSpace (options .Workdir ),
205+ SessionID : strings .TrimSpace (options .SessionID ),
206+ ListenAddress : strings .TrimSpace (options .ListenAddress ),
207+ })
208+ },
209+ }
210+ command .Flags ().StringVar (& options .Prompt , "prompt" , "" , "run prompt text" )
211+ command .Flags ().StringVar (& options .Workdir , "workdir" , "" , "workspace absolute path" )
212+ command .Flags ().StringVar (& options .SessionID , "session-id" , "" , "existing session id for resume-only wake" )
213+ command .Flags ().StringVar (
214+ & options .ListenAddress ,
215+ "listen" ,
216+ urlscheme .DefaultHTTPDaemonListenAddress ,
217+ "http daemon listen address" ,
218+ )
219+ return command
220+ }
221+
222+ // newDaemonEncodeReviewCommand 创建 daemon encode review 子命令。
223+ func newDaemonEncodeReviewCommand () * cobra.Command {
224+ options := & daemonEncodeCommandOptions {Action : "review" }
225+ command := & cobra.Command {
226+ Use : "review" ,
227+ Short : "Encode wake review URL" ,
228+ SilenceUsage : true ,
229+ SilenceErrors : true ,
230+ Args : cobra .NoArgs ,
231+ RunE : func (cmd * cobra.Command , args []string ) error {
232+ return runDaemonEncodeCommand (cmd .Context (), daemonEncodeCommandOptions {
233+ Action : "review" ,
234+ Path : strings .TrimSpace (options .Path ),
235+ Workdir : strings .TrimSpace (options .Workdir ),
236+ SessionID : strings .TrimSpace (options .SessionID ),
237+ ListenAddress : strings .TrimSpace (options .ListenAddress ),
238+ })
239+ },
240+ }
241+ command .Flags ().StringVar (& options .Path , "path" , "" , "review file path" )
242+ command .Flags ().StringVar (& options .Workdir , "workdir" , "" , "workspace absolute path" )
243+ command .Flags ().StringVar (& options .SessionID , "session-id" , "" , "existing session id for resume-only wake" )
244+ command .Flags ().StringVar (
245+ & options .ListenAddress ,
246+ "listen" ,
247+ urlscheme .DefaultHTTPDaemonListenAddress ,
248+ "http daemon listen address" ,
249+ )
250+ return command
251+ }
252+
162253// defaultDaemonServeCommandRunner 启动 HTTP daemon 主循环。
163254func defaultDaemonServeCommandRunner (ctx context.Context , options daemonServeCommandOptions ) error {
164255 if serveHTTPDaemon == nil {
@@ -239,3 +330,76 @@ func defaultDaemonStatusCommandRunner(ctx context.Context, options daemonStatusC
239330 "hosts_alias_configured" : status .HostsAliasConfigured ,
240331 })
241332}
333+
334+ // defaultDaemonEncodeCommandRunner 生成 URL 编码后的 daemon 唤醒链接,并输出单行 URL。
335+ func defaultDaemonEncodeCommandRunner (_ context.Context , options daemonEncodeCommandOptions ) error {
336+ urlText , err := buildDaemonEncodedWakeURL (options )
337+ if err != nil {
338+ return err
339+ }
340+ _ , _ = fmt .Fprintln (os .Stdout , urlText )
341+ return nil
342+ }
343+
344+ // buildDaemonEncodedWakeURL 按 action 组装标准化的可点击 HTTP 唤醒链接。
345+ func buildDaemonEncodedWakeURL (options daemonEncodeCommandOptions ) (string , error ) {
346+ action := strings .ToLower (strings .TrimSpace (options .Action ))
347+ if action != "run" && action != "review" {
348+ return "" , fmt .Errorf ("unsupported encode action: %s" , options .Action )
349+ }
350+
351+ sessionID := strings .TrimSpace (options .SessionID )
352+ workdir := strings .TrimSpace (options .Workdir )
353+ query := neturl.Values {}
354+ switch action {
355+ case "run" :
356+ prompt := strings .TrimSpace (options .Prompt )
357+ if sessionID == "" && prompt == "" {
358+ return "" , errors .New ("missing required flag: --prompt" )
359+ }
360+ if prompt != "" {
361+ query .Set ("prompt" , prompt )
362+ }
363+ case "review" :
364+ path := strings .TrimSpace (options .Path )
365+ if sessionID == "" && path == "" {
366+ return "" , errors .New ("missing required flag: --path" )
367+ }
368+ if sessionID == "" && workdir == "" {
369+ return "" , errors .New ("missing required flag: --workdir (or provide --session-id)" )
370+ }
371+ if path != "" {
372+ query .Set ("path" , path )
373+ }
374+ }
375+
376+ if workdir != "" {
377+ query .Set ("workdir" , workdir )
378+ }
379+ if sessionID != "" {
380+ query .Set ("session_id" , sessionID )
381+ }
382+
383+ hostPort := daemonEncodeHostPort (options .ListenAddress )
384+ return (& neturl.URL {
385+ Scheme : "http" ,
386+ Host : hostPort ,
387+ Path : "/" + action ,
388+ RawQuery : query .Encode (),
389+ }).String (), nil
390+ }
391+
392+ // daemonEncodeHostPort 将监听地址归一为对外文档可点击的 neocode 域名地址。
393+ func daemonEncodeHostPort (listenAddress string ) string {
394+ normalized := strings .TrimSpace (listenAddress )
395+ if normalized == "" {
396+ normalized = urlscheme .DefaultHTTPDaemonListenAddress
397+ }
398+ if _ , port , err := net .SplitHostPort (normalized ); err == nil && strings .TrimSpace (port ) != "" {
399+ return net .JoinHostPort (urlscheme .DaemonHostsAlias , strings .TrimSpace (port ))
400+ }
401+ if strings .IndexByte (normalized , ':' ) < 0 && normalized != "" {
402+ return net .JoinHostPort (urlscheme .DaemonHostsAlias , normalized )
403+ }
404+ return net .JoinHostPort (urlscheme .DaemonHostsAlias , "18921" )
405+ }
0 commit comments