client-exec CLI
This commit is contained in:
@@ -10,7 +10,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -220,17 +219,8 @@ func (m *Manager) clientTools(l *live) []mcp.Tool {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) recentClients() []clients.Client {
|
||||
rows := m.clients.List()
|
||||
sort.SliceStable(rows, func(i, j int) bool {
|
||||
a, b := rows[i].LastActionAt, rows[j].LastActionAt
|
||||
return a != nil && (b == nil || a.After(*b))
|
||||
})
|
||||
return rows
|
||||
}
|
||||
|
||||
func (m *Manager) listClients() string {
|
||||
rows := m.recentClients()
|
||||
rows := m.clients.Recent()
|
||||
if len(rows) == 0 {
|
||||
return "No client Macs are connected."
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
How a script or plain shell runs a command on a client Mac without an agent (byre client-exec), and how to check either Mac's screen lock (byre screen).
|
||||
|
||||
# Running commands on a client Mac from a shell
|
||||
|
||||
`client_bash` needs an agent. For a script or plain shell on the machine byre runs on, such as a wrapper that fetches a token from the human's Mac, use:
|
||||
|
||||
```
|
||||
byre client-exec [--client NAME] [--timeout SECS] -- CMD...
|
||||
```
|
||||
|
||||
`byre` is `/Applications/Byre.app/Contents/Helpers/byre` on every Mac with the app.
|
||||
|
||||
- It runs CMD as `client_bash` does: zsh as a login shell in the home directory, in the app's session, as the human. That session is what GUI-only commands need.
|
||||
- Without `--client` it picks the most recently active client, the one `list_clients` lists first.
|
||||
- Each argument reaches the client as one word, so shell syntax needs `-- zsh -c '...'`.
|
||||
- The command's stdout and stderr come back separately and byte for byte, once it ends, written to client-exec's own stdout and stderr.
|
||||
- Exit status: the command's own; 75 when no client is connected, it didn't acknowledge the request within 2s, or it disconnected; 124 when `--timeout` (default 30s) killed it. A one-line `byre client-exec: ...` on stderr says why.
|
||||
|
||||
## Credentials
|
||||
|
||||
It needs `BYRE_TOKEN`, which byre sets for everything it starts: agents, their tools and the processes those run, and the task's terminals. A process byre didn't start reads it from `loopback-token` in byre's data directory (`~/Library/Application Support/byre/` by default); it changes every time byre restarts, so read it at each call:
|
||||
|
||||
```
|
||||
BYRE_TOKEN=$(<"$HOME/Library/Application Support/byre/loopback-token") byre client-exec -- ...
|
||||
```
|
||||
|
||||
byre logs each call (client, command, exit status, duration), never its output.
|
||||
|
||||
## Screen lock
|
||||
|
||||
`byre screen` prints `unlocked`, `locked` or `no-session` (no GUI login for this user) for the Mac it runs on, and exits 0. Check the server Mac with `byre screen` and the client with:
|
||||
|
||||
```
|
||||
byre client-exec -- /Applications/Byre.app/Contents/Helpers/byre screen
|
||||
```
|
||||
|
||||
client-exec runs commands whatever the lock state; a script that needs an unlocked screen checks first and fails fast itself.
|
||||
@@ -15,7 +15,7 @@ const signTimeout = 2 * time.Minute
|
||||
func (m *Manager) signer(ctx context.Context, user api.User) func(payload []byte) ([]byte, error) {
|
||||
return func(payload []byte) ([]byte, error) {
|
||||
mine := []clients.Client{}
|
||||
for _, c := range m.recentClients() {
|
||||
for _, c := range m.clients.Recent() {
|
||||
if c.Login == user.Login {
|
||||
mine = append(mine, c)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user