@@ -417,13 +417,14 @@ Mailbox:
417417 pilotctl received [--clear]
418418 pilotctl inbox [--clear]
419419
420- Scriptorium (low-level responder dispatch):
420+ Service Agents (AI-powered overlay services):
421+ pilotctl tui Interactive TUI for all service agents
422+ pilotctl ls List available service agents and config status
423+
421424 pilotctl scriptorium <command> [body] [--node <address|hostname>]
422425 pilotctl scriptorium polymarket "from: 2026-04-01T00:00:00Z, to: 2026-04-02T00:00:00Z"
423426 pilotctl scriptorium stockmarket "from: 2026-04-01"
424- Config: ~/.pilot/scriptorium.yaml (node: <pilot-address>)
425427
426- Service Agents (AI-powered overlay services):
427428 pilotctl ai "<query>" [--node <address>] [--timeout <dur>] [--output-file [path]]
428429 pilotctl clawdit ["<query>"] [--file <openclaw.json>] [--node <address>] [--timeout <dur>] [--output-file [path]]
429430
@@ -799,6 +800,10 @@ func main() {
799800 cmdAi (cmdArgs )
800801 case "clawdit" :
801802 cmdClawdit (cmdArgs )
803+ case "ls" :
804+ cmdLs (cmdArgs )
805+ case "tui" :
806+ cmdTui (cmdArgs )
802807
803808 // Internal: forked daemon process
804809 case "_daemon-run" :
@@ -1132,6 +1137,31 @@ func cmdContext() {
11321137 "description" : "List messages received via data exchange (port 1001). Messages saved to ~/.pilot/inbox/. Use --clear to delete all" ,
11331138 "returns" : "messages [{type, from, data, received_at}], total, dir" ,
11341139 },
1140+ "scriptorium" : map [string ]interface {}{
1141+ "args" : []string {"<command>" , "[body]" , "[--node <address|hostname>]" },
1142+ "description" : "Send a command to a Scriptorium service agent (e.g. stockmarket, polymarket)" ,
1143+ "returns" : "command, body, node, ack" ,
1144+ },
1145+ "ai" : map [string ]interface {}{
1146+ "args" : []string {"<query>" , "[--node <address>]" , "[--timeout <dur>]" , "[--output-file [path]]" },
1147+ "description" : "Send a natural-language query to the AI assistant and wait for the reply" ,
1148+ "returns" : "reply" ,
1149+ },
1150+ "clawdit" : map [string ]interface {}{
1151+ "args" : []string {"[query]" , "[--file <openclaw.json>]" , "[--node <address>]" , "[--timeout <dur>]" , "[--output-file [path]]" },
1152+ "description" : "Run a security audit of an OpenClaw installation" ,
1153+ "returns" : "reply" ,
1154+ },
1155+ "ls" : map [string ]interface {}{
1156+ "args" : []string {},
1157+ "description" : "List all available service agents and their configuration status" ,
1158+ "returns" : "agents [{name, description, usage, config, configured, node}]" ,
1159+ },
1160+ "tui" : map [string ]interface {}{
1161+ "args" : []string {},
1162+ "description" : "Launch the interactive service-agent TUI (chat with AI, query Scriptorium, run audits)" ,
1163+ "returns" : "(interactive — no JSON output)" ,
1164+ },
11351165 },
11361166 "error_codes" : map [string ]interface {}{
11371167 "invalid_argument" : "Bad input or usage error (do not retry)" ,
@@ -5529,6 +5559,124 @@ func writeServiceAgentReply(reply, outputFile, prefix string) {
55295559 }
55305560}
55315561
5562+ // cmdLs lists all available service agents and their configuration status.
5563+ func cmdLs (args []string ) {
5564+ type agentInfo struct {
5565+ Name string `json:"name"`
5566+ Description string `json:"description"`
5567+ Usage string `json:"usage"`
5568+ Config string `json:"config"`
5569+ Configured bool `json:"configured"`
5570+ Node string `json:"node,omitempty"`
5571+ }
5572+
5573+ agents := []agentInfo {
5574+ {
5575+ Name : "ai" ,
5576+ Description : "Natural-language assistant — ask anything about your network" ,
5577+ Usage : `pilotctl ai "<query>"` ,
5578+ Config : "~/.pilot/scriptorium.yaml" ,
5579+ },
5580+ {
5581+ Name : "scriptorium" ,
5582+ Description : "Intelligence briefs (stockmarket, polymarket, …)" ,
5583+ Usage : `pilotctl scriptorium <command> "<body>"` ,
5584+ Config : "~/.pilot/scriptorium.yaml" ,
5585+ },
5586+ {
5587+ Name : "clawdit" ,
5588+ Description : "Security audit of an OpenClaw installation" ,
5589+ Usage : `pilotctl clawdit ["<query>"] [--file <path>]` ,
5590+ Config : "~/.pilot/clawdit.yaml" ,
5591+ },
5592+ }
5593+
5594+ // Resolve configured state.
5595+ scriptoriumNode := loadScriptoriumNode ()
5596+ clawditNode := loadClawditNode ()
5597+ for i := range agents {
5598+ switch agents [i ].Name {
5599+ case "ai" , "scriptorium" :
5600+ if scriptoriumNode != "" {
5601+ agents [i ].Configured = true
5602+ agents [i ].Node = scriptoriumNode
5603+ }
5604+ case "clawdit" :
5605+ if clawditNode != "" {
5606+ agents [i ].Configured = true
5607+ agents [i ].Node = clawditNode
5608+ }
5609+ }
5610+ }
5611+
5612+ if jsonOutput {
5613+ outputOK (map [string ]interface {}{"agents" : agents })
5614+ return
5615+ }
5616+
5617+ fmt .Println ("Service Agents:" )
5618+ fmt .Println ()
5619+ for _ , a := range agents {
5620+ status := " ✗ not configured"
5621+ if a .Configured {
5622+ status = fmt .Sprintf (" ✓ node %s" , a .Node )
5623+ }
5624+ fmt .Printf (" %-14s %s\n " , a .Name , a .Description )
5625+ fmt .Printf (" %-14s %s\n " , "" , a .Usage )
5626+ fmt .Printf (" %-14s config: %s%s\n " , "" , a .Config , status )
5627+ fmt .Println ()
5628+ }
5629+ }
5630+
5631+ // cmdTui launches the interactive service-agent TUI.
5632+ func cmdTui (args []string ) {
5633+ // Locate tui.py: check next to the binary first, then fall back to ~/.pilot/tui.py.
5634+ exe , _ := os .Executable ()
5635+ candidates := []string {
5636+ filepath .Join (filepath .Dir (exe ), "tui.py" ),
5637+ }
5638+ home , err := os .UserHomeDir ()
5639+ if err == nil {
5640+ candidates = append (candidates , filepath .Join (home , ".pilot" , "tui.py" ))
5641+ }
5642+
5643+ var tuiPath string
5644+ for _ , p := range candidates {
5645+ if _ , err := os .Stat (p ); err == nil {
5646+ tuiPath = p
5647+ break
5648+ }
5649+ }
5650+ if tuiPath == "" {
5651+ fatalHint ("not_found" ,
5652+ "ensure tui.py is installed at ~/.pilot/tui.py or next to the pilotctl binary" ,
5653+ "TUI not found" )
5654+ }
5655+
5656+ // Find python3.
5657+ python := "python3"
5658+ if p , err := exec .LookPath ("python3" ); err == nil {
5659+ python = p
5660+ } else if p , err := exec .LookPath ("python" ); err == nil {
5661+ python = p
5662+ } else {
5663+ fatalHint ("not_found" ,
5664+ "install Python 3: https://python.org" ,
5665+ "python3 not found on PATH" )
5666+ }
5667+
5668+ cmd := exec .Command (python , tuiPath )
5669+ cmd .Stdin = os .Stdin
5670+ cmd .Stdout = os .Stdout
5671+ cmd .Stderr = os .Stderr
5672+ if err := cmd .Run (); err != nil {
5673+ if exitErr , ok := err .(* exec.ExitError ); ok {
5674+ os .Exit (exitErr .ExitCode ())
5675+ }
5676+ fatalCode ("internal" , "tui: %v" , err )
5677+ }
5678+ }
5679+
55325680// loadClawditNode reads the target node from ~/.pilot/clawdit.yaml.
55335681func loadClawditNode () string {
55345682 return loadPilotYAMLNode ("clawdit.yaml" )
0 commit comments