Skip to content

Commit 529cee7

Browse files
authored
Initial implementation (#1)
* Initial implementation * CI: run "go test" * CI: check for missing "go generate" * Test on macOS Tahoe because we use cgo * Integration test that uses Xvfb and a Fyne app * CI: nicer name for main.yml workflow * Disable TestSimpleVNC because of chicken and egg * Implement explicit Run() method for desktop.Desktop and use errgroup * TestSimpleVNC: only skip it in CI, also enable log/slog debug logging * A bit more logging when receiving framebuffer updates and CV results * TestSimpleVNC: test all mtell capabilities, not just <click 'text'> * Executor: wait a bit before requesting an image and trying CV again * vision: log found text using debug level
1 parent fb9ac2d commit 529cee7

27 files changed

Lines changed: 3652 additions & 3 deletions

File tree

.github/workflows/main.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Main
2+
3+
on:
4+
push:
5+
6+
jobs:
7+
test:
8+
name: Test
9+
runs-on: ghcr.io/cirruslabs/macos-runner:tahoe
10+
steps:
11+
- uses: actions/checkout@v4
12+
- uses: actions/setup-go@v5
13+
with:
14+
go-version: stable
15+
- name: Run tests
16+
run: go test -v ./...
17+
missing-go-generate-check:
18+
name: Check for missing "go generate"
19+
runs-on: ghcr.io/cirruslabs/ubuntu-runner-amd64:24.04-xs
20+
steps:
21+
- uses: actions/checkout@v4
22+
- uses: actions/setup-go@v5
23+
with:
24+
go-version: stable
25+
- name: Run "go generate ./..."
26+
run: go generate ./...
27+
- name: Make sure no files had been changed
28+
run: |
29+
git status --porcelain
30+
test -z "$(git status --porcelain)"

README.md

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,61 @@
11
# mtell
22

3-
`mtell` CLI allows you to tell a machine to do something over VNC.
4-
5-
It's indispensable when the actions to be performed cannot be done over a traditional SSH connection, which is a frequent necessity when automating macOS machines.
3+
`mtell` CLI allows you to tell a machine to do something over VNC. It's indispensable when the actions to be performed cannot be done over a traditional SSH connection, which is a frequent necessity when automating macOS machines.
64

75
Besides allowing you to script basic keyboard and mouse interactions, `mtell` can also utilize computer vision to wait for and click certain elements on the screen.
6+
7+
This project is heavily inspired by [Packer's `boot_command`](https://developer.hashicorp.com/packer/integrations/cirruslabs/tart/latest/components/builder/tart#boot-configuration), but extends its command set and allows you to run these commands in any environment where you can start a binary.
8+
9+
Special thanks to [Tor Arne Vestbø](https://github.com/torarnv), who contributed the initial [`<wait 'text'>` implementation](https://github.com/cirruslabs/packer-plugin-tart/pull/178) to [Packer builder for Tart VMs](https://github.com/cirruslabs/packer-plugin-tart), which made us realize that we can further extend the `boot_command` and do pretty cool things with it (e.g. computer vision).
10+
11+
## Installation
12+
13+
### Using Golang
14+
15+
```shell
16+
go install github.com/cirruslabs/mtell@latest
17+
```
18+
19+
## Usage
20+
21+
```shell
22+
mtell --vnc "vnc://:password@localhost:5900" "<wait10s><click 'Select Your Country or Region'>"
23+
```
24+
25+
## Reference
26+
27+
### Waiting
28+
29+
There commands are useful to work around loading screens.
30+
31+
* `<wait10>` — wait 10 seconds
32+
* `<wait5m15s>` — wait 5 minutes and 15 seconds
33+
* `<wait 'Choose Your Country'>` — using computer vision, wait for the pattern (can be a regular expression) to appear on screen
34+
35+
### Keyboard
36+
37+
Introduce the following commands into your program to press a corresponding key:
38+
39+
* `<bs>`, `<del>`, `<enter>`, `<return>`, `<esc>`, `<tab>`, `<spacebar>` — editing keys
40+
* `<insert>`, `<home>`, `<end>`, `<pageUp>`, `<pageDown>` — navigation keys
41+
* `<up>`, `<down>`, `<left>`, `<right>` — arrow keys
42+
* `<f1>``<f12>` — <kbd>F1</kbd>–<kbd>F12</kbd> keys
43+
* `<menu>` — context menu key
44+
* `<leftAlt>`, `<rightAlt>` — <kbd>Alt</kbd> key
45+
* `<leftCtrl>`, `<rightCtrl>` — <kbd>Control</kbd> key
46+
* `<leftShift>`, `<rightShift>` — <kbd>Shift</kbd> key
47+
* `<leftSuper>`, `<rightSuper>` — <kbd>Super</kbd> key
48+
* `<leftCommand>`, `<rightCommand>` — <kbd>⌘</kbd> key on macOS
49+
* `<leftOption>`, `<rightOption>` — <kbd>⌥</kbd> key on macOS
50+
51+
Any keyboard command can be modified with `On` or `Off` modifier, for example:
52+
53+
* `<leftShift>` — presses <kbd>Shift</kbd> key and releases it
54+
* `<leftShiftOn>` — presses <kbd>Shift</kbd> key without releasing it
55+
* `<leftShiftOff>` — releases <kbd>Shift</kbd> key
56+
57+
### Mouse
58+
59+
These commands allow one to utilize a mouse, currently only through computer vision:
60+
61+
* `<click 'Accept'>` — using computer vision, wait for the pattern (can be a regular expression) to appear on screen and click in the center of its bounding box

cmd/main.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log/slog"
7+
"os"
8+
"os/signal"
9+
"syscall"
10+
11+
"github.com/cirruslabs/mtell/internal/command"
12+
"github.com/cirruslabs/mtell/internal/logginglevel"
13+
"github.com/lmittmann/tint"
14+
)
15+
16+
func main() {
17+
if err := mainImpl(); err != nil {
18+
fmt.Fprintln(os.Stderr, err)
19+
20+
os.Exit(1)
21+
}
22+
}
23+
24+
func mainImpl() error {
25+
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
26+
defer cancel()
27+
28+
// Use a colored logging handler for log/slog
29+
handler := tint.NewHandler(os.Stderr, &tint.Options{
30+
Level: logginglevel.Level,
31+
})
32+
logger := slog.New(handler)
33+
slog.SetDefault(logger)
34+
35+
return command.NewRootCommand().ExecuteContext(ctx)
36+
}

go.mod

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
module github.com/cirruslabs/mtell
2+
3+
go 1.26
4+
5+
require (
6+
github.com/lmittmann/tint v1.1.3
7+
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed
8+
github.com/mna/pigeon v1.3.0
9+
github.com/progrium/darwinkit v0.5.0
10+
github.com/spf13/cobra v1.10.2
11+
github.com/stretchr/testify v1.11.1
12+
golang.org/x/sync v0.19.0
13+
)
14+
15+
require (
16+
dario.cat/mergo v1.0.2 // indirect
17+
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
18+
github.com/Microsoft/go-winio v0.6.2 // indirect
19+
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
20+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
21+
github.com/containerd/errdefs v1.0.0 // indirect
22+
github.com/containerd/errdefs/pkg v0.3.0 // indirect
23+
github.com/containerd/log v0.1.0 // indirect
24+
github.com/containerd/platforms v0.2.1 // indirect
25+
github.com/cpuguy83/dockercfg v0.3.2 // indirect
26+
github.com/distribution/reference v0.6.0 // indirect
27+
github.com/docker/docker v28.5.1+incompatible // indirect
28+
github.com/docker/go-connections v0.6.0 // indirect
29+
github.com/docker/go-units v0.5.0 // indirect
30+
github.com/ebitengine/purego v0.8.4 // indirect
31+
github.com/felixge/httpsnoop v1.0.4 // indirect
32+
github.com/go-logr/logr v1.4.3 // indirect
33+
github.com/go-logr/stdr v1.2.2 // indirect
34+
github.com/go-ole/go-ole v1.2.6 // indirect
35+
github.com/google/uuid v1.6.0 // indirect
36+
github.com/klauspost/compress v1.18.0 // indirect
37+
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
38+
github.com/magiconair/properties v1.8.10 // indirect
39+
github.com/moby/docker-image-spec v1.3.1 // indirect
40+
github.com/moby/go-archive v0.1.0 // indirect
41+
github.com/moby/patternmatcher v0.6.0 // indirect
42+
github.com/moby/sys/sequential v0.6.0 // indirect
43+
github.com/moby/sys/user v0.4.0 // indirect
44+
github.com/moby/sys/userns v0.1.0 // indirect
45+
github.com/moby/term v0.5.0 // indirect
46+
github.com/morikuni/aec v1.0.0 // indirect
47+
github.com/opencontainers/go-digest v1.0.0 // indirect
48+
github.com/opencontainers/image-spec v1.1.1 // indirect
49+
github.com/pkg/errors v0.9.1 // indirect
50+
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
51+
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
52+
github.com/sirupsen/logrus v1.9.3 // indirect
53+
github.com/tklauser/go-sysconf v0.3.12 // indirect
54+
github.com/tklauser/numcpus v0.6.1 // indirect
55+
github.com/yusufpapurcu/wmi v1.2.4 // indirect
56+
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
57+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
58+
go.opentelemetry.io/otel v1.40.0 // indirect
59+
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect
60+
go.opentelemetry.io/otel/metric v1.40.0 // indirect
61+
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
62+
go.opentelemetry.io/otel/trace v1.40.0 // indirect
63+
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
64+
golang.org/x/crypto v0.43.0 // indirect
65+
golang.org/x/sys v0.40.0 // indirect
66+
google.golang.org/protobuf v1.36.11 // indirect
67+
)
68+
69+
require (
70+
github.com/davecgh/go-spew v1.1.1 // indirect
71+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
72+
github.com/pmezard/go-difflib v1.0.0 // indirect
73+
github.com/spf13/pflag v1.0.9 // indirect
74+
github.com/testcontainers/testcontainers-go v0.40.0
75+
golang.org/x/mod v0.21.0 // indirect
76+
golang.org/x/tools v0.25.0 // indirect
77+
gopkg.in/yaml.v3 v3.0.1 // indirect
78+
)

0 commit comments

Comments
 (0)