445 lines
19 KiB
Markdown
445 lines
19 KiB
Markdown
# Cthulhu Tentacles
|
|
|
|
A repository of experimental Tentacles for Cthulhu, maintained by Storm
|
|
Dragon. It contains [Hello World](hello-world/README.md) and the development
|
|
[Wormhole tentacle](wormhole/README.md).
|
|
Repository material and both Tentacles are licensed under GPL-3.0-or-later; see
|
|
[LICENSE](LICENSE) and [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
|
|
`repository.toml` identifies this source. `catalog.toml` explicitly lists its
|
|
Tentacles, each in a direct top-level folder with its own `tentacle.toml`.
|
|
The catalog lists native packages for Linux amd64 and arm64. Hello World 0.1.0 is
|
|
available from the [release page](https://git.stormux.org/storm/cthulhu-tentacles/releases/tag/hello-world-v0.1.0).
|
|
Wormhole 0.1.2 requires the matching reader with interactive Tentacle settings support.
|
|
Native packages are available from the
|
|
[Wormhole release page](https://git.stormux.org/storm/cthulhu-tentacles/releases/tag/wormhole-v0.1.2).
|
|
Local build artifacts live in the ignored `dist/` directory.
|
|
The discovery source URL is
|
|
`https://git.stormux.org/storm/cthulhu-tentacles`.
|
|
|
|
Tentacles are independent of the Git hosting service. Discovery reads TOML files
|
|
from an HTTPS Git repository; installation downloads the HTTPS URLs explicitly
|
|
listed in its catalog. Packages can live in pretty much any git provider,
|
|
on an HTTPS web server, or in object storage with stable public HTTPS
|
|
links. The reader does not call a hosting service's release API. Keeping packages
|
|
in separate downloads avoids putting binaries into Git history.
|
|
|
|
To write your own, follow the [creation and release tutorial](#create-and-release-your-own-tentacle).
|
|
|
|
## Build and check
|
|
|
|
Keep the matching experimental `cthulhu` checkout beside this directory.
|
|
The `go.mod` local replacement uses its Go SDK, including the new
|
|
uninterruptible presentation option. Building requires Go 1.24 or newer;
|
|
checks also require Python 3.11 or newer and Make.
|
|
|
|
```sh
|
|
make build
|
|
make check
|
|
```
|
|
|
|
The executables are `hello-world/bin/hello-world` and
|
|
`wormhole/bin/wormhole-tentacle`; both are ignored by Git.
|
|
Checks validate the TOML metadata, registration and suggested bindings, and
|
|
invoke both Tentacles through the actual Cthulhu host. They produce no speech
|
|
or Braille and leave the running reader alone.
|
|
|
|
## Repository installation
|
|
|
|
With the updated reader, open global Cthulhu Preferences → Tentacles → Add
|
|
repository and enter the HTTPS Git URL above. Refresh an already added source.
|
|
Select Hello World or Wormhole and press Space to review its source/version and
|
|
requested access, then **Install and enable**. Wormhole requests internal forms
|
|
and focused-item/clipboard access as well as speech/Braille. **Install** instead downloads
|
|
and saves the package disabled. The reader verifies the catalog's SHA-256 digest,
|
|
package identity and native architecture before saving a launch entry. Catalogs
|
|
without a published compatible release show a clear message and remain unchecked.
|
|
No source builds or install scripts run during this workflow. Resource limits
|
|
do not make untrusted same-user programs safe.
|
|
|
|
After installation, refresh status until Active and use Cthulhu+Alt+Shift+H.
|
|
Disable retains the package; re-enabling and reader startup use installed files
|
|
without network access. Removing the source retains installed Tentacles. Removing
|
|
a managed Tentacle stops it and deletes its package files after shutdown.
|
|
Package updates are not implemented in this slice. See the
|
|
[Hello World live checks](hello-world/README.md#live-verification).
|
|
|
|
## Prepare and publish packages
|
|
|
|
```sh
|
|
make check
|
|
make package
|
|
```
|
|
|
|
`tools/package.py` cross-builds statically linked native binaries for Linux amd64
|
|
and arm64 from the matching adjacent SDK. It creates deterministic archives
|
|
containing `tentacle.toml`, `bin/hello-world`, `README.md` and `LICENSE`, plus
|
|
`dist/SHA256SUMS` and a generated `dist/catalog.toml`. Go builds use readonly
|
|
module metadata, trim paths and omit VCS stamping. To build one architecture:
|
|
|
|
```sh
|
|
python3 -B tools/package.py --architecture amd64
|
|
```
|
|
|
|
Validate the actual host-architecture package through Cthulhu's installer
|
|
and command host, without changing the running reader:
|
|
|
|
```sh
|
|
cd ../cthulhu
|
|
CTHULHU_TENTACLE_RELEASE_ROOT=../cthulhu-tentacles GOCACHE=/tmp/cthulhu-cache go test ./internal/tentacle -run '^TestBuiltHelloWorldPackageThroughHost$' -count=1 -v
|
|
```
|
|
|
|
Before publication, review both repositories' changes and contributing guides.
|
|
Commit the reviewed source with the matching SDK available; select that source
|
|
revision for the `hello-world-v0.1.0` release. In the Git hosting release page,
|
|
upload `dist/hello-world-0.1.0-linux-amd64.tar.gz`,
|
|
`dist/hello-world-0.1.0-linux-arm64.tar.gz` and `dist/SHA256SUMS`. Publish and verify
|
|
the download URLs and digests **before** publishing the matching catalog change.
|
|
Copy the reviewed `dist/catalog.toml` to `catalog.toml`, review the diff, and
|
|
submit/push it explicitly. A release filename/version must never be reused for
|
|
changed content; bump the version before preparing a later release.
|
|
|
|
The package command creates local artifacts only. It does not create commits,
|
|
releases, tags, uploads or pushes. Prepared catalog changes are not evidence that
|
|
the corresponding release is already available. Live shortcut, speech, Braille,
|
|
lock/handoff and interruption checks remain necessary after installation. An
|
|
explicit restart is needed when changing the reader itself. For source-only local
|
|
testing, the development launch procedure remains in
|
|
[Hello World](hello-world/README.md#local-launch).
|
|
|
|
## Create and release your own Tentacle
|
|
|
|
This tutorial creates a native Go Tentacle named **Greeting**, then makes it
|
|
installable from a repository. The SDK and package format are experimental;
|
|
use a matching Cthulhu checkout. Currently the installer supports native Linux
|
|
executables, protocol 1.0, command registration and optional presentation access.
|
|
Python SDK development is separate from this native package workflow. Broader
|
|
APIs and automatic package updates are still pending.
|
|
|
|
### 1. Set up the source repository
|
|
|
|
You need Go 1.24 or newer, Git, Python 3.11 or newer for this repository's checks,
|
|
GNU tar and gzip. Keep your new repository beside `cthulhu`. From their parent
|
|
directory:
|
|
|
|
```sh
|
|
mkdir -p greeting-tentacles/greeting/src
|
|
cd greeting-tentacles
|
|
git init -b main
|
|
```
|
|
|
|
Create these files:
|
|
|
|
```text
|
|
greeting-tentacles/
|
|
repository.toml
|
|
catalog.toml
|
|
go.mod
|
|
.gitignore
|
|
greeting/
|
|
tentacle.toml
|
|
README.md
|
|
LICENSE
|
|
src/main.go
|
|
```
|
|
|
|
Use this `go.mod`:
|
|
|
|
```go
|
|
module example.org/greeting-tentacles
|
|
|
|
go 1.24.0
|
|
|
|
require git.stormux.org/storm/cthulhu v0.0.0
|
|
|
|
replace git.stormux.org/storm/cthulhu => ../cthulhu
|
|
```
|
|
|
|
The SDK import path names its source project; it does not constrain where you
|
|
host your own repository or downloads. Record the tested reader/SDK revision in
|
|
release notes, since this local replacement does not pin it. Add `/dist/` and
|
|
`/greeting/bin/` to `.gitignore`.
|
|
|
|
Give `greeting/README.md` a description, shortcut, requirements and test procedure.
|
|
Put the full license text in `greeting/LICENSE`; choose terms you have permission
|
|
to distribute. For an example derived from Hello World, preserve its notices and
|
|
GPL-3.0-or-later license. Include source corresponding to your executable.
|
|
|
|
Create `repository.toml`:
|
|
|
|
```toml
|
|
format-version = 1
|
|
id = "example.greeting-tentacles"
|
|
name = "Greeting Tentacles"
|
|
description = "A small example Tentacle repository."
|
|
```
|
|
|
|
Create the initial `catalog.toml`:
|
|
|
|
```toml
|
|
format-version = 1
|
|
|
|
[[tentacles]]
|
|
id = "greeting"
|
|
manifest = "greeting/tentacle.toml"
|
|
```
|
|
|
|
Manifests belong in direct top-level folders. Repository and Tentacle IDs start
|
|
with a lowercase letter and contain only lowercase letters, digits, dots and
|
|
hyphens, up to 128 characters. Keep IDs stable: the reader uses the source URL,
|
|
repository ID and Tentacle ID to identify an installation. A catalog can list
|
|
several Tentacles, each with its own manifest and releases. Entries without
|
|
releases can be discovered, but cannot yet be installed.
|
|
|
|
### 2. Describe the package and permissions
|
|
|
|
Create `greeting/tentacle.toml`, replacing the example source URL with your own
|
|
HTTPS Git URL before publication:
|
|
|
|
```toml
|
|
format-version = 1
|
|
id = "greeting"
|
|
name = "Greeting"
|
|
description = "Speak and braille a short greeting."
|
|
version = "0.1.0"
|
|
authors = ["Contributor"]
|
|
license = "GPL-3.0-or-later"
|
|
source = "https://example.org/projects/greeting-tentacles.git"
|
|
help = "README.md"
|
|
runtime = "native"
|
|
platforms = ["linux"]
|
|
entry-point = ["bin/greeting"]
|
|
apis = ["commands", "presentation"]
|
|
capabilities = ["presentation"]
|
|
|
|
[protocol]
|
|
major = 1
|
|
minor = 0
|
|
```
|
|
|
|
Paths refer to the archive's root, not the source repository's root. The entry
|
|
point is an executable followed by optional literal arguments, not a shell
|
|
command. Presentation access allows speech and Braille results through the
|
|
reader. A command-only package uses `apis = ["commands"]` and
|
|
`capabilities = []`, without requesting presentation in command registration.
|
|
Other package API or capability combinations are currently unsupported.
|
|
|
|
### 3. Implement a command
|
|
|
|
Create `greeting/src/main.go`:
|
|
|
|
```go
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
|
|
"git.stormux.org/storm/cthulhu/sdk/go/tentacle"
|
|
)
|
|
|
|
func say_greeting(ctx context.Context, _ json.RawMessage) (any, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return tentacle.Presentation{
|
|
Speech: "Hello from Greeting!",
|
|
Braille: "Hello from Greeting!",
|
|
}, nil
|
|
}
|
|
|
|
func main() {
|
|
command := tentacle.Command{
|
|
ID: "greet", Title: "Greeting",
|
|
Description: "Speak and braille a greeting", Presentation: true,
|
|
Bindings: []tentacle.Binding{
|
|
{Layout: "desktop", Scope: "global", Key: "Cthulhu+Ctrl+Alt+F10"},
|
|
{Layout: "laptop", Scope: "global", Key: "Cthulhu+Ctrl+Alt+F10"},
|
|
},
|
|
Handle: say_greeting,
|
|
}
|
|
if tentacle.Serve(os.Stdin, os.Stdout, []tentacle.Command{command}) != nil {
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
```
|
|
|
|
The SDK handles the JSON-RPC session and registration. Stdout belongs exclusively
|
|
to that protocol; send limited diagnostics to stderr. Respect the supplied
|
|
context when doing work, including network requests. The reader owns input,
|
|
speech, Braille, user settings and lifecycle safeguards. Use its public SDK
|
|
instead of reader internals or output backends. Core and user bindings take
|
|
precedence over suggested shortcuts; users can remap Tentacle commands.
|
|
|
|
Greeting uses ordinary interruptible presentation. Hello World demonstrates
|
|
`Uninterruptible: true`; use that deliberately, rather than for every message.
|
|
See its [output behavior](hello-world/README.md) for cancellation, the 30-second
|
|
protection bound and speech backend limitations.
|
|
|
|
### 4. Build and test before packaging
|
|
|
|
From `greeting-tentacles`:
|
|
|
|
```sh
|
|
gofmt -w greeting/src/main.go
|
|
mkdir -p greeting/bin
|
|
CGO_ENABLED=0 GOCACHE=/tmp/cthulhu-cache go build -buildvcs=false -mod=readonly -trimpath -o greeting/bin/greeting ./greeting/src
|
|
GOCACHE=/tmp/cthulhu-cache go test ./...
|
|
```
|
|
|
|
Invoke the compiled command through the development host. From `cthulhu`:
|
|
|
|
```sh
|
|
GOCACHE=/tmp/cthulhu-cache go run ./cmd/tentacle-host -presentation -command greet "$PWD/../greeting-tentacles/greeting/bin/greeting"
|
|
```
|
|
|
|
Expect JSON containing `speech` and `braille`, both `Hello from Greeting!`.
|
|
This runs the executable through the actual command host, but produces no speech
|
|
or Braille. The harness's default mode has no resource containment; use it only
|
|
with development code you trust. Protocol tests do not establish live behavior.
|
|
|
|
For a live test, follow [Hello World's development launch procedure](hello-world/README.md#local-launch),
|
|
substituting your absolute executable path and launch ID `greeting`, with
|
|
`Presentation: true` in the launch JSON. `--replace --tentacles` replaces the
|
|
current Go reader and selects the development list instead of the saved registry;
|
|
run it explicitly when ready. Rebuild the reader first if its implementation
|
|
changed. Production activation requires delegated cgroup resources and does not
|
|
silently fall back when delegation fails.
|
|
|
|
Test the shortcut, learn mode, remapping, ordinary reading and web navigation on
|
|
Xorg. Verify speech modes, interruption, screen lock, terminal handoff, disable
|
|
and connected Braille where affected. See [CONTRIBUTING.md](CONTRIBUTING.md) for
|
|
review and regression requirements; contributors remain responsible for reviewing
|
|
and testing AI-assisted changes.
|
|
|
|
### 5. Create architecture-specific archives
|
|
|
|
The `make build`, `make check` and `tools/package.py` commands in **this**
|
|
repository build/check Hello World and Wormhole. `tools/package.py --tentacle wormhole`
|
|
packages Wormhole; the default still packages Hello World. The checker pins the
|
|
current versions and expected Tentacles. Adapt those tools and checks when
|
|
extending this repository, or use
|
|
this standalone procedure for Greeting.
|
|
|
|
From `greeting-tentacles`, stage the root manifest, documentation and license:
|
|
|
|
```sh
|
|
mkdir -p dist/greeting-package/bin
|
|
cp greeting/tentacle.toml greeting/README.md greeting/LICENSE dist/greeting-package/
|
|
```
|
|
|
|
Run the following in Bash for version `0.1.0`. Change the version together with
|
|
the manifest on later releases. `set -euo pipefail` stops on build/archive failures.
|
|
|
|
```bash
|
|
set -euo pipefail
|
|
version=0.1.0
|
|
for architecture in amd64 arm64; do
|
|
CGO_ENABLED=0 GOOS=linux GOARCH="$architecture" GOCACHE=/tmp/cthulhu-cache \
|
|
go build -buildvcs=false -mod=readonly -trimpath -ldflags='-s -w' \
|
|
-o dist/greeting-package/bin/greeting ./greeting/src
|
|
tar --sort=name --mtime=@0 --owner=0 --group=0 --numeric-owner \
|
|
-C dist/greeting-package -cf - tentacle.toml README.md LICENSE bin \
|
|
| gzip -n > "dist/greeting-$version-linux-$architecture.tar.gz"
|
|
done
|
|
sha256sum "dist/greeting-$version-linux-amd64.tar.gz" \
|
|
"dist/greeting-$version-linux-arm64.tar.gz" \
|
|
| sed 's@ dist/@ @' > dist/SHA256SUMS
|
|
```
|
|
|
|
Each archive must have this layout, without an enclosing `greeting/` folder:
|
|
|
|
```text
|
|
tentacle.toml
|
|
README.md
|
|
LICENSE
|
|
bin/greeting
|
|
```
|
|
|
|
The executable must be a regular, executable Linux ELF file for the advertised
|
|
architecture. Static Go builds avoid undeclared shared-library dependencies.
|
|
The reader rejects unsafe paths, symbolic/hard links and special files. Current
|
|
limits are 32 MiB per compressed download, 64 MiB expanded file data and 1,024
|
|
archive entries. Installation runs no install scripts or source builds. Package
|
|
ID, name, version and capabilities must match the repository manifest.
|
|
|
|
Inspect each archive with `tar -tzf`; from `dist`, run `sha256sum -c SHA256SUMS`.
|
|
Run each extracted executable through the host on its matching architecture;
|
|
a cross-build alone is not an execution test. The staging executable above ends
|
|
with the last architecture built, so use the appropriate extracted archive for
|
|
your local test. Keep the exact archives for publication. Normalized tar/gzip
|
|
metadata supports repeatable packaging; compare hashes when rebuilding with the
|
|
same source and toolchain.
|
|
|
|
### 6. Publish downloads, then advertise them
|
|
|
|
Review the source diff and tests before committing. Push reviewed source to your
|
|
chosen Git host. For later versions, use a release branch while preparing assets
|
|
so the default branch's existing manifest and catalog remain consistent. Create
|
|
a release/tag such as `greeting-v0.1.0` targeting that source commit. If using a
|
|
separate download server, record that source revision alongside the files.
|
|
Upload both `.tar.gz` files and `SHA256SUMS`, and make the release public when the
|
|
assets are ready. Keep `dist/` and executables out of Git.
|
|
|
|
Use stable direct HTTPS download URLs. Release attachments are convenient but
|
|
optional; an ordinary HTTPS file server works too. URLs cannot embed credentials
|
|
or fragments, and redirects must remain HTTPS. The reader has no authenticated
|
|
private-download workflow; expiring signed URLs are unsuitable for a durable
|
|
catalog.
|
|
|
|
Download the published files again and check `sha256sum -c SHA256SUMS` against
|
|
those downloads. Check the archive manifest and executable. Only then append
|
|
these tables after Greeting's entry in `catalog.toml`:
|
|
|
|
```toml
|
|
[[tentacles.releases]]
|
|
platform = "linux"
|
|
architecture = "amd64"
|
|
url = "https://downloads.example.org/greeting/0.1.0/greeting-0.1.0-linux-amd64.tar.gz"
|
|
sha256 = "REPLACE_WITH_THE_AMD64_ARCHIVE_SHA256"
|
|
|
|
[[tentacles.releases]]
|
|
platform = "linux"
|
|
architecture = "arm64"
|
|
url = "https://downloads.example.org/greeting/0.1.0/greeting-0.1.0-linux-arm64.tar.gz"
|
|
sha256 = "REPLACE_WITH_THE_ARM64_ARCHIVE_SHA256"
|
|
```
|
|
|
|
Replace the URLs and placeholders with published URLs and their actual
|
|
64-character lowercase hexadecimal hashes. These tables belong to the preceding
|
|
`[[tentacles]]`; each platform/architecture pair may appear once. Keep the
|
|
manifest version equal to the version inside the packages. Include only
|
|
architectures you actually publish. For several Tentacles, preserve the existing
|
|
entries: this repository's generated `dist/catalog.toml` preserves catalog entries and prepares
|
|
release metadata for the selected Tentacles. Review it before publication.
|
|
|
|
Review and push the matching catalog and manifest together to the repository's
|
|
default branch, which discovery fetches. Never replace a release file with changed
|
|
content: increase the version, create new archives and URLs, then publish a new
|
|
catalog revision. Refresh updates available metadata, not installed packages.
|
|
To test a newer version with the current reader, remove that managed Tentacle
|
|
and install it again after refreshing the source.
|
|
|
|
### 7. Verify the published installation
|
|
|
|
Open global Preferences → Tentacles → Add repository in the matching reader and
|
|
enter your HTTPS Git URL, or refresh an existing source. Select Greeting and
|
|
press Space. Check source, version and presentation access in the first-use
|
|
dialog, then choose **Install and enable**. Confirm status becomes Active and
|
|
test Cthulhu+Ctrl+Alt+F10 and the live checks from step 4.
|
|
|
|
Also check **Install** without enabling, enable/disable, offline reader restart
|
|
and removal. Disabled packages retain their files; installed packages launch
|
|
offline. Removing a source retains installed packages; removing a managed
|
|
Tentacle stops it and deletes its package files. Keep ordinary reader commands,
|
|
keyboard navigation and speech/Braille preferences working.
|
|
|
|
For no compatible release, check architecture and refresh the source. For
|
|
download/checksum errors, verify the exact public URL and digest. For package
|
|
metadata errors, compare the archive manifest with the repository manifest. For
|
|
activation failures, check the reader log for protocol, executable or cgroup
|
|
errors. Resource limits constrain consumption; they do not sandbox access to
|
|
account files or network. Install publishers you trust and state any remaining
|
|
unverified live behavior in release notes.
|