Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/installer-verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Verify installers

on:
pull_request:
paths:
- "install*.ps1"
- "install*.sh"
- "uninstall*.ps1"
- "uninstall*.sh"
- "scripts/verify-installers.ps1"
- "src/Ai.Tlbx.MidTerm.Common/Identity/**"
- "src/Ai.Tlbx.MidTerm/Settings/**"
- "src/Ai.Tlbx.MidTerm/Startup/**"
- "src/Ai.Tlbx.MidTerm/Services/Certificates/**"
- "src/Ai.Tlbx.MidTerm/Services/Updates/**"
push:
branches: [main, dev]
paths:
- "install*.ps1"
- "install*.sh"
- "uninstall*.ps1"
- "uninstall*.sh"
- "scripts/verify-installers.ps1"
- "src/Ai.Tlbx.MidTerm.Common/Identity/**"
- "src/Ai.Tlbx.MidTerm/Settings/**"
- "src/Ai.Tlbx.MidTerm/Startup/**"
- "src/Ai.Tlbx.MidTerm/Services/Certificates/**"
- "src/Ai.Tlbx.MidTerm/Services/Updates/**"

permissions:
contents: read

jobs:
verify:
strategy:
matrix:
os: [ubuntu-24.04, windows-2025]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
- name: Verify installer contracts
shell: pwsh
run: ./scripts/verify-installers.ps1
6 changes: 3 additions & 3 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ jobs:
source: ./docs/site
destination: ./_site

- name: Make Pages artifact writable
run: sudo chown -R "$(id -u):$(id -g)" ./_site

- name: Publish canonical signed-release installers
run: |
cp ./install.sh ./_site/install.sh
Expand All @@ -44,9 +47,6 @@ jobs:
test -f ./_site/uninstall.sh
test -f ./_site/uninstall.ps1

- name: Make Pages artifact writable
run: sudo chown -R "$(id -u):$(id -g)" ./_site

- name: Normalize and verify duplicate Pages search metadata
shell: python
run: |
Expand Down
32 changes: 20 additions & 12 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -642,9 +642,14 @@ jobs:
Copy-Item "src/Ai.Tlbx.MidTerm/bin/Release/net10.0/$rid/publish/mt.exe" "staging/$rid/"
Copy-Item "src/Ai.Tlbx.MidTerm.TmuxShim/bin/Release/net10.0/$rid/publish/mttmux.exe" "staging/$rid/"
Copy-Item src/version.json "staging/$rid/"
Copy-Item "src/Ai.Tlbx.MidTerm/src/static/THIRD-PARTY-LICENSES.txt" "staging/$rid/"
$mthostPath = "src/Ai.Tlbx.MidTerm.TtyHost/bin/Release/net10.0/$rid/publish/mthost.exe"
if (Test-Path $mthostPath) {
Copy-Item $mthostPath "staging/$rid/"
./scripts/copy-windows-conpty-runtime.ps1 `
-SourceDir (Split-Path -Parent $mthostPath) `
-DestinationDir "staging/$rid" `
-Rid $rid
} else {
Write-Host "mthost.exe not found for $rid; continuing with web-only artifact set"
}
Expand All @@ -654,13 +659,12 @@ jobs:
shell: pwsh
run: |
$rid = '${{ matrix.rid }}'
$files = @("staging/$rid/mt.exe", "staging/$rid/mtagenthost.exe", "staging/$rid/mttmux.exe")
if (Test-Path "staging/$rid/mthost.exe") {
$files += "staging/$rid/mthost.exe"
}
$hashes = Get-ChildItem $files | ForEach-Object {
$root = (Resolve-Path "staging/$rid").Path
$files = Get-ChildItem $root -Recurse -File | Where-Object Name -NotIn @("version.json", "SHA256SUMS.txt")
$hashes = $files | ForEach-Object {
$hash = (Get-FileHash $_ -Algorithm SHA256).Hash.ToLower()
"$hash $($_.Name)"
$relativePath = [IO.Path]::GetRelativePath($root, $_.FullName).Replace('\', '/')
"$hash $relativePath"
}
$hashes | Set-Content "staging/$rid/SHA256SUMS.txt"
Write-Host "=== $rid ==="
Expand Down Expand Up @@ -743,9 +747,14 @@ jobs:
Copy-Item "src/Ai.Tlbx.MidTerm/bin/Release/net10.0/$rid/publish/mt.exe" "staging/$rid/"
Copy-Item "src/Ai.Tlbx.MidTerm.TmuxShim/bin/Release/net10.0/$rid/publish/mttmux.exe" "staging/$rid/"
Copy-Item src/version.json "staging/$rid/"
Copy-Item "src/Ai.Tlbx.MidTerm/src/static/THIRD-PARTY-LICENSES.txt" "staging/$rid/"
$mthostPath = "src/Ai.Tlbx.MidTerm.TtyHost/bin/Release/net10.0/$rid/publish/mthost.exe"
if (Test-Path $mthostPath) {
Copy-Item $mthostPath "staging/$rid/"
./scripts/copy-windows-conpty-runtime.ps1 `
-SourceDir (Split-Path -Parent $mthostPath) `
-DestinationDir "staging/$rid" `
-Rid $rid
} else {
Write-Host "mthost.exe not found for $rid; continuing with web-only artifact set"
}
Expand Down Expand Up @@ -789,13 +798,12 @@ jobs:
shell: pwsh
run: |
$rid = '${{ matrix.rid }}'
$files = @("staging/$rid/mt.exe", "staging/$rid/mtagenthost.exe", "staging/$rid/mttmux.exe")
if (Test-Path "staging/$rid/mthost.exe") {
$files += "staging/$rid/mthost.exe"
}
$hashes = Get-ChildItem $files | ForEach-Object {
$root = (Resolve-Path "staging/$rid").Path
$files = Get-ChildItem $root -Recurse -File | Where-Object Name -NotIn @("version.json", "SHA256SUMS.txt")
$hashes = $files | ForEach-Object {
$hash = (Get-FileHash $_ -Algorithm SHA256).Hash.ToLower()
"$hash $($_.Name)"
$relativePath = [IO.Path]::GetRelativePath($root, $_.FullName).Replace('\', '/')
"$hash $relativePath"
}
$hashes | Set-Content "staging/$rid/SHA256SUMS.txt"
Write-Host "=== $rid ==="
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ curl -fsSL https://get.tlbx.ai/install.sh | bash
irm https://get.tlbx.ai/install.ps1 | iex
```

Open `https://localhost:2000`. Choose service mode for a host that should survive logouts and reboots; user mode needs no administrator access.
The installer starts tlbx, waits until its process and HTTPS health endpoint are ready, and then prints clickable localhost and available Tailscale URLs for the configured port. Choose service mode for a host that should survive logouts and reboots; user mode needs no administrator access.

These commands install the current stable release. Add `--dev` on macOS/Linux or `-Dev` on Windows only when you explicitly want the prerelease channel.

Expand Down
2 changes: 2 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,8 @@ The root `install.ps1` and `install.sh` scripts handle:
- certificate reuse plus trust flows for both newly generated and reused certificates
- platform-specific install paths and service registration
- channel selection and release download
- detached user-mode startup, supervised service startup, and fatal process/HTTPS readiness verification
- ready-to-open localhost plus reachable Tailscale URLs for the configured bind address and port
- update logging

### Update Service
Expand Down
2 changes: 1 addition & 1 deletion docs/AgentControllerSessionDesign.md
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@ Status in this branch/work item:
- implemented: the history pane itself is again the native local pixel scroller for the currently materialized kernel, while browse-mode retained-window growth and trims preserve the reader anchor
- implemented: ordinary local pane scrolling no longer force-refreshes the already loaded history window when no window shift is needed; forced same-window refetch is reserved for urgent void-recovery cases where the viewport has lost all intersecting concrete rows
- implemented: direct progress-nav scrubs now jump to a tiny centered preview window first and then hydrate into a normal browse window after drag idle, so large jumps do not try to materialize the traversed span
- implemented gap: canonical interactive request/question flows now have a dedicated frontend interview widget, but the backend model still represents them as request summaries rather than a first-class canonical `interview` item type
- implemented: canonical interactive request/question flows are first-class `interview` history items with embedded questions and answers; the frontend prefers that self-renderable payload and retains request-summary lookup only as a compatibility fallback

Still mandatory after this work whenever Agent Controller Session evolves:

Expand Down
5 changes: 3 additions & 2 deletions docs/ai/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,9 @@ tlbx is a web-based terminal multiplexer. Native AOT compiled, runs on macOS/Win
- `mthost` / `mthost.exe` — TTY host (spawned per terminal, all platforms)

**Settings locations:**
- Service mode: `%ProgramData%\MidTerm\settings.json` (Win) or `/usr/local/etc/midterm/settings.json` (Unix)
- User mode: `~/.midterm/settings.json`
- Service mode: `%ProgramData%\tlbx\settings.json` (Win) or `/usr/local/etc/tlbx/settings.json` (Unix)
- User mode: `~/.tlbx/settings.json`
- Existing legacy installations continue to use their `MidTerm` / `.midterm` paths in place.

**Platform defaults:**
- Windows — ConPTY, default shell `Pwsh`
Expand Down
5 changes: 3 additions & 2 deletions docs/ai/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ tlbx is a web-based terminal multiplexer. Native AOT compiled, runs on macOS/Win
- `mthost` / `mthost.exe` — TTY host (spawned per terminal, all platforms)

**Settings locations:**
- Service mode: `%ProgramData%\MidTerm\settings.json` (Win) or `/usr/local/etc/midterm/settings.json` (Unix)
- User mode: `~/.midterm/settings.json`
- Service mode: `%ProgramData%\tlbx\settings.json` (Win) or `/usr/local/etc/tlbx/settings.json` (Unix)
- User mode: `~/.tlbx/settings.json`
- Existing legacy installations continue to use their `MidTerm` / `.midterm` paths in place.

**Platform defaults:**
- Windows — ConPTY, default shell `Pwsh`
Expand Down
84 changes: 45 additions & 39 deletions docs/multi-instance-install.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ Use the normal installer when:
- the service should stay at the default identity and port
- you are upgrading an existing normal installation

Do not mix installer families for the same instance. A normal `MidTerm` service
should be managed by the normal installer. `MidTerm-<name>`,
`midterm-<name>`, and `ai.tlbx.midterm.<name>` services should be managed by
Do not mix installer families for the same instance. A normal `tlbx` service
should be managed by the normal installer. `tlbx-<name>` and
`ai.tlbx.instance.<name>` services should be managed by
the multi-instance installer.

## Platform and architecture support
Expand Down Expand Up @@ -95,9 +95,9 @@ For an instance named `alice`, the generated identities are:

| Platform | Service identity |
| --- | --- |
| Windows | `MidTerm-alice` |
| Linux | `midterm-alice` |
| macOS | `ai.tlbx.midterm.alice` |
| Windows | `tlbx-alice` |
| Linux | `tlbx-alice` |
| macOS | `ai.tlbx.instance.alice` |

The settings directory contains an `instance.json` manifest with the effective
name, port, bind address, service identity, install directory, settings
Expand All @@ -109,19 +109,19 @@ Windows defaults:

| Purpose | Default |
| --- | --- |
| Settings root | `%ProgramData%\MidTerm\instances` |
| Install root | `%ProgramFiles%\MidTerm\instances` |
| Instance settings | `%ProgramData%\MidTerm\instances\<name>` |
| Instance binaries | `%ProgramFiles%\MidTerm\instances\<name>` |
| Settings root | `%ProgramData%\tlbx\instances` |
| Install root | `%ProgramFiles%\tlbx\instances` |
| Instance settings | `%ProgramData%\tlbx\instances\<name>` |
| Instance binaries | `%ProgramFiles%\tlbx\instances\<name>` |

macOS and Linux defaults:

| Purpose | Default |
| --- | --- |
| Settings root | `/usr/local/etc/midterm-instances` |
| Install root | `/usr/local/lib/midterm/instances` |
| Instance settings | `/usr/local/etc/midterm-instances/<name>` |
| Instance binaries | `/usr/local/lib/midterm/instances/<name>` |
| Settings root | `/usr/local/etc/tlbx-instances` |
| Install root | `/usr/local/lib/tlbx/instances` |
| Instance settings | `/usr/local/etc/tlbx-instances/<name>` |
| Instance binaries | `/usr/local/lib/tlbx/instances/<name>` |

These roots are intentionally separate from the normal installer paths. Do not
point a multi-instance install at the normal service directory.
Expand Down Expand Up @@ -149,8 +149,8 @@ Run from an elevated PowerShell session.
| `-BasePort` | `int` | `2000` | `install`, `plan` | First candidate port when `-Ports` is not provided. Used ports are skipped. |
| `-Ports` | `int[]` | empty | `install`, `plan`, `update`, `remove` | Explicit ports. Must contain exactly one port per resolved instance name. |
| `-BindAddress` | `string` | `0.0.0.0` | `install`, `update`, `update-all`, `plan` | Address passed to `mt --bind`. Use `127.0.0.1` for local-only access. |
| `-RootDir` | `string` | `%ProgramData%\MidTerm\instances` | all | Root for instance settings and manifests. |
| `-InstallRoot` | `string` | `%ProgramFiles%\MidTerm\instances` | install/update/remove | Root for instance binaries. |
| `-RootDir` | `string` | `%ProgramData%\tlbx\instances` | all | Root for instance settings and manifests. |
| `-InstallRoot` | `string` | `%ProgramFiles%\tlbx\instances` | install/update/remove | Root for instance binaries. |
| `-VersionTag` | `string` | `latest` | install/update/update-all | GitHub release tag, for example `v9.18.0-dev`. |
| `-AssetPath` | `string` | empty | install/update/update-all | Local zip asset. Overrides GitHub download. |
| `-PasswordHash` | `string` | empty | install | Precomputed PBKDF2 password hash. Preferred for automated agents. |
Expand Down Expand Up @@ -182,8 +182,8 @@ sudo ./install-multi.sh --mode remove --names alice
| `--base-port` | `N` | `2000` | `install`, `plan` | First candidate port when `--ports` is not provided. Used ports are skipped when `ss` or `lsof` is available. |
| `--ports` | `p1,p2,p3` | empty | `install`, `plan`, `update`, `remove` | Explicit ports. Must contain exactly one port per resolved instance name. |
| `--bind` | address | `0.0.0.0` | `install`, `update`, `update-all`, `plan` | Address passed to `mt --bind`. Use `127.0.0.1` for local-only access. |
| `--root-dir` | path | `/usr/local/etc/midterm-instances` | all | Root for instance settings and manifests. |
| `--install-root` | path | `/usr/local/lib/midterm/instances` | install/update/remove | Root for instance binaries. |
| `--root-dir` | path | `/usr/local/etc/tlbx-instances` | all | Root for instance settings and manifests. |
| `--install-root` | path | `/usr/local/lib/tlbx/instances` | install/update/remove | Root for instance binaries. |
| `--version-tag` | tag | `latest` | install/update/update-all | GitHub release tag, for example `v9.18.0-dev`. |
| `--asset-path` | path | empty | install/update/update-all | Local tar.gz asset. Overrides GitHub download. |
| `--password-hash` | hash | empty | install | Precomputed PBKDF2 password hash. Preferred for automated agents. |
Expand All @@ -200,14 +200,16 @@ valid for manual diagnostics.

| Runtime flag | Environment variable | Meaning |
| --- | --- | --- |
| `--port <port>` | `MIDTERM_PORT` | HTTPS listener port. |
| `--bind <address>` | `MIDTERM_BIND` | Listener bind address. |
| `--settings-dir <path>` | `MIDTERM_SETTINGS_DIR` | Settings, secrets, certs, logs, sessions, update state. |
| `--service-mode` | `MIDTERM_SERVICE_MODE=true` | Force service-mode paths and secret storage behavior. |
| `--user-mode` | `MIDTERM_SERVICE_MODE=false` | Force user-mode behavior. Do not use for installed multi-instance services. |
| `--service-name <name>` | `MIDTERM_SERVICE_NAME` | Windows service identity used by updates and restarts. |
| `--launchd-label <label>` | `MIDTERM_LAUNCHD_LABEL` | macOS launchd identity used by updates and restarts. |
| `--systemd-service <name>` | `MIDTERM_SYSTEMD_SERVICE` | Linux systemd unit used by updates and restarts. |
| `--port <port>` | `TLBX_PORT` | HTTPS listener port. |
| `--bind <address>` | `TLBX_BIND` | Listener bind address. |
| `--settings-dir <path>` | `TLBX_SETTINGS_DIR` | Settings, secrets, certs, logs, sessions, update state. |
| `--service-mode` | `TLBX_SERVICE_MODE=true` | Force service-mode paths and secret storage behavior. |
| `--user-mode` | `TLBX_SERVICE_MODE=false` | Force user-mode behavior. Do not use for installed multi-instance services. |
| `--service-name <name>` | `TLBX_SERVICE_NAME` | Windows service identity used by updates and restarts. |
| `--launchd-label <label>` | `TLBX_LAUNCHD_LABEL` | macOS launchd identity used by updates and restarts. |
| `--systemd-service <name>` | `TLBX_SYSTEMD_SERVICE` | Linux systemd unit used by updates and restarts. |

The legacy `MIDTERM_*` environment-variable aliases remain accepted for existing automation; when both forms are set, `TLBX_*` wins.

For multi-instance services, always pass `--settings-dir` and `--service-mode`.
Do not rely on default settings paths, because defaults resolve to the normal
Expand Down Expand Up @@ -428,25 +430,25 @@ File system:
Windows:

```powershell
Get-Service MidTerm-alice
Restart-Service MidTerm-alice
Stop-Service MidTerm-alice
Start-Service MidTerm-alice
Get-Service tlbx-alice
Restart-Service tlbx-alice
Stop-Service tlbx-alice
Start-Service tlbx-alice
```

Linux:

```bash
systemctl status midterm-alice
sudo systemctl restart midterm-alice
sudo journalctl -u midterm-alice -n 200 --no-pager
systemctl status tlbx-alice
sudo systemctl restart tlbx-alice
sudo journalctl -u tlbx-alice -n 200 --no-pager
```

macOS:

```bash
sudo launchctl print system/ai.tlbx.midterm.alice
sudo launchctl kickstart -k system/ai.tlbx.midterm.alice
sudo launchctl print system/ai.tlbx.instance.alice
sudo launchctl kickstart -k system/ai.tlbx.instance.alice
sudo log show --predicate 'process == "mt"' --last 10m
```

Expand All @@ -464,7 +466,7 @@ For each instance:
8. Updating one instance does not change the binary timestamp or version of
another instance.
9. Stopping one service does not stop another service.
10. The normal `MidTerm` service on port `2000` is unchanged unless it was
10. The normal `tlbx` service on port `2000` is unchanged unless it was
intentionally part of the plan.

PowerShell endpoint check:
Expand Down Expand Up @@ -529,13 +531,17 @@ The multi-instance installer is intentionally separate from the normal
installer. Do not change these normal-install invariants while working on
multi-instance behavior:

- default Windows service name remains `MidTerm`
- default macOS launchd label remains `ai.tlbx.midterm`
- default Linux service name remains `MidTerm`
- default Windows service name remains `tlbx`
- default macOS launchd label remains `ai.tlbx.service`
- default Linux service name remains `tlbx`
- default port remains `2000`
- default service settings paths remain the normal installer paths
- `install.ps1` and `install.sh` keep their single-instance behavior

The installers automatically keep the legacy names and roots when updating an
existing MidTerm multi-instance layout; fresh installations use the tlbx names
shown above.

When changing multi-instance support, verify at least one plan-mode command and
review changes for accidental edits to the normal installer path.

Expand Down
Loading
Loading