Add list tools; normalize tool names and paths in transcript

This commit is contained in:
Ian Gulliver
2026-10-20 21:05:29 -07:00
parent e5a7fc4c1a
commit 53f20e40d3
9 changed files with 175 additions and 20 deletions
+57 -14
View File
@@ -181,6 +181,21 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
return m.clientGrep(ctx, raw)
},
},
{
Name: "client_list",
Description: listDescription + " It lists a directory on a client Mac, with the byre in its installed Byre.app.",
InputSchema: json.RawMessage(`{
"type": "object",
"properties": {
"client": {"type": "string", "description": "The client's name from list_clients, or its id when several are connected from that Mac."},
"path": {"type": "string", "description": "The directory, absolute or starting with ~/."}
},
"required": ["client", "path"]
}`),
Handler: func(ctx context.Context, raw json.RawMessage) (string, error) {
return m.clientList(ctx, raw)
},
},
{
Name: "client_hands",
Description: "Call one of your hands tools (screen_list, capture, ax_tree, act, browser, chrome) on a client Mac instead of the Mac byre runs on: " +
@@ -552,39 +567,67 @@ func (m *Manager) clientGrep(ctx context.Context, raw json.RawMessage) (string,
if err != nil {
return "", fmt.Errorf("malformed client_grep call: %w", err)
}
err = clientPath("path", args.Path)
path, err := clientShellPath(args.Path)
if err != nil {
return "", err
}
path := shellQuote(args.Path)
rest, ok := strings.CutPrefix(args.Path, "~/")
if ok {
path = "~/" + shellQuote(rest)
}
command := clientByre + " grep"
flags := ""
if args.Glob != "" {
command += " --glob " + shellQuote(args.Glob)
flags += " --glob " + shellQuote(args.Glob)
}
if args.IgnoreCase {
command += " --ignore-case"
flags += " --ignore-case"
}
if args.FilesOnly {
command += " --files-only"
flags += " --files-only"
}
command += " -- " + shellQuote(args.Pattern) + " " + path
return m.runClientByre(ctx, args.Client, "grep", flags+" -- "+shellQuote(args.Pattern)+" "+path)
}
type clientListArgs struct {
Client string `json:"client"`
Path string `json:"path"`
}
func (m *Manager) clientList(ctx context.Context, raw json.RawMessage) (string, error) {
args := clientListArgs{}
err := json.Unmarshal(raw, &args)
if err != nil {
return "", fmt.Errorf("malformed client_list call: %w", err)
}
path, err := clientShellPath(args.Path)
if err != nil {
return "", err
}
return m.runClientByre(ctx, args.Client, "list", " "+path)
}
func clientShellPath(p string) (string, error) {
err := clientPath("path", p)
if err != nil {
return "", err
}
rest, ok := strings.CutPrefix(p, "~/")
if ok {
return "~/" + shellQuote(rest), nil
}
return shellQuote(p), nil
}
func (m *Manager) runClientByre(ctx context.Context, client, sub, args string) (string, error) {
command := clientByre + " " + sub + args
ms := int(fileTimeout.Milliseconds())
cctx, cancel := context.WithTimeout(ctx, fileTimeout+replyGrace)
defer cancel()
reply, err := m.clients.Call(cctx, args.Client, api.ClientRequest{Op: api.Bash, Command: &command, TimeoutMs: &ms})
reply, err := m.clients.Call(cctx, client, api.ClientRequest{Op: api.Bash, Command: &command, TimeoutMs: &ms})
if err != nil {
return "", err
}
if reply.ExitCode == nil || reply.Output == nil {
return "", fmt.Errorf("%s's reply has no exit_code or output", args.Client)
return "", fmt.Errorf("%s's reply has no exit_code or output", client)
}
if *reply.ExitCode != 0 {
return "", fmt.Errorf("byre grep on %s exited %d: %s", args.Client, *reply.ExitCode, *reply.Output)
return "", fmt.Errorf("byre %s on %s exited %d: %s", sub, client, *reply.ExitCode, *reply.Output)
}
return *reply.Output, nil
}