Files

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.