Rebrand: Nexus Terminal → Alchementary Link

Renames the product across the codebase: Go module path, config/data
paths, keychain service name, export file extension, build tooling,
docs, and UI text. Sidebar brand mark now reads ">_ AlchementaryLink"
with "Alchementary" in the connection-group gold accent colour, and
the window title is "Alchementary Link".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019T8bJS8BCtY5GeSG2HBj1n
This commit is contained in:
Andy Dixon
2026-09-03 16:41:23 +01:00
co-authored by Claude Sonnet 5
parent 114060127f
commit 1a380a0e99
135 changed files with 678 additions and 674 deletions
+5 -5
View File
@@ -1,10 +1,10 @@
# Copilot Instructions — Nexus Terminal
# Copilot Instructions — Alchementary Link
## Build & Test
```bash
make deps # go mod download + npm install
make build # production build → build/bin/nexus-terminal.app
make build # production build → build/bin/alchementary-link.app
make dev # wails dev with hot reload
make test # go test ./... -v -count=1
make lint # golangci-lint run ./...
@@ -31,7 +31,7 @@ Wails v2 desktop app: Go backend + HTML/CSS/JS frontend rendered in a native web
- `sftpclient/` — SFTP file operations (browse, upload, download, rename, delete)
- `sshconfig/` — OpenSSH `~/.ssh/config` parser and importer
- `recording/` — asciicast v2 session recording and playback
- `exportimport/` — encrypted profile bundles (.nexus format)
- `exportimport/` — encrypted profile bundles (.link format)
- `updater/` — GitHub Releases update checker
- `knownhosts/` — SSH host key management
- `validation/` — profile and protocol validation
@@ -40,7 +40,7 @@ Wails v2 desktop app: Go backend + HTML/CSS/JS frontend rendered in a native web
**Frontend** (`frontend/`):
- Plain HTML/CSS/JS — no framework, no bundler, no ES modules
- `index.html` is the app shell; loads CSS then JS in strict order
- `src/js/` — 18 modules, each IIFE-wrapped, exporting to `window.NexusTerminal`
- `src/js/` — 18 modules, each IIFE-wrapped, exporting to `window.AlchementaryLink`
- `src/css/` — modular stylesheets; `tokens.css` defines all design tokens
- Vendor libs (xterm.js, addons) live in `src/js/lib/` and `src/css/lib/` — auto-copied, gitignored
@@ -77,7 +77,7 @@ The macOS webview silently blocks or breaks several standard web APIs. These con
- Tests use `t.TempDir()` for isolation, `t.Fatalf()` for setup failures
**Frontend patterns:**
- Every JS file is IIFE-wrapped and exports to `window.NexusTerminal` namespace
- Every JS file is IIFE-wrapped and exports to `window.AlchementaryLink` namespace
- Script load order matters (defined in `index.html`) — utils → state → notifications → sidebar → tabs → themes → terminal → profiles → raw → github → settings → commands → secrets → recordings → sftp → splitpane → snippets → app
- DOM helpers: `createElement(tag, className, attrs)` from utils.js (supports `textContent`, `innerHTML`, `dataset`, `on*` event handlers)
- Profile editor uses `_renderSection(title, fields)` pattern with `_field(type, name, label, value, opts)` helper for form generation
+8 -8
View File
@@ -100,35 +100,35 @@ jobs:
- name: Package (zip)
if: matrix.archive_type == 'zip' && matrix.runner != 'windows-latest'
run: |
archive="nexus-terminal-${{ matrix.platform }}.zip"
archive="alchementary-link-${{ matrix.platform }}.zip"
cd build/bin
zip -r "../../${archive}" .
- name: Package (tar)
if: matrix.archive_type == 'tar'
run: |
archive="nexus-terminal-${{ matrix.platform }}.tar.gz"
archive="alchementary-link-${{ matrix.platform }}.tar.gz"
tar -czf "${archive}" -C build/bin .
- name: Package (Windows zip)
if: matrix.archive_type == 'zip' && matrix.runner == 'windows-latest'
shell: pwsh
run: |
$archive = "nexus-terminal-${{ matrix.platform }}.zip"
$archive = "alchementary-link-${{ matrix.platform }}.zip"
Compress-Archive -Path "build/bin/*" -DestinationPath $archive
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: nexus-terminal-${{ matrix.platform }}
name: alchementary-link-${{ matrix.platform }}
path: |
nexus-terminal-${{ matrix.platform }}.zip
nexus-terminal-${{ matrix.platform }}.tar.gz
alchementary-link-${{ matrix.platform }}.zip
alchementary-link-${{ matrix.platform }}.tar.gz
- name: Attach to GitHub Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v2
with:
files: |
nexus-terminal-${{ matrix.platform }}.zip
nexus-terminal-${{ matrix.platform }}.tar.gz
alchementary-link-${{ matrix.platform }}.zip
alchementary-link-${{ matrix.platform }}.tar.gz
+1 -1
View File
@@ -1,5 +1,5 @@
# ==========================================================================
# Nexus Terminal — .gitignore
# Alchementary Link — .gitignore
# ==========================================================================
# ── Build output ──────────────────────────────────────────────────────────
+8 -8
View File
@@ -3,9 +3,9 @@
# Resolve the Wails binary. Prefer wails on PATH; fall back to GOPATH/bin.
WAILS := $(or $(shell which wails 2>/dev/null),$(shell go env GOPATH)/bin/wails)
VERSION := $(shell git describe --tags --abbrev=0 2>/dev/null || echo dev)-$(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
LD_UPDATE_URL := $(if $(NEXUS_UPDATE_URL),-X github.com/andydixon/nexus/internal/api.appUpdateURL=$(NEXUS_UPDATE_URL),)
LD_UPDATE_URL := $(if $(LINK_UPDATE_URL),-X github.com/andydixon/link/internal/api.appUpdateURL=$(LINK_UPDATE_URL),)
UPX_FLAGS ?=
NEXUS_FORCE_MACOS_UPX ?= 0
LINK_FORCE_MACOS_UPX ?= 0
GOBIN_DIR := $(shell go env GOPATH)/bin
export PATH := $(GOBIN_DIR):$(PATH)
@@ -18,11 +18,11 @@ deps: frontend-deps
upx-check:
@command -v upx >/dev/null 2>&1 || { echo "UPX is required for compressed builds. Install it first, for example: brew install upx"; exit 1; }
@if [ "$$(uname -s)" = "Darwin" ] && [ "$(NEXUS_FORCE_MACOS_UPX)" != "1" ]; then \
echo "UPX compression is disabled for macOS by default. Use ./release.sh for a zipped .app, or run NEXUS_FORCE_MACOS_UPX=1 UPX_FLAGS='--force-macos --best --no-color --no-progress' make build-compressed."; \
@if [ "$$(uname -s)" = "Darwin" ] && [ "$(LINK_FORCE_MACOS_UPX)" != "1" ]; then \
echo "UPX compression is disabled for macOS by default. Use ./release.sh for a zipped .app, or run LINK_FORCE_MACOS_UPX=1 UPX_FLAGS='--force-macos --best --no-color --no-progress' make build-compressed."; \
exit 1; \
fi
@if [ "$$(uname -s)" = "Darwin" ] && [ "$(NEXUS_FORCE_MACOS_UPX)" = "1" ] && ! printf '%s\n' "$(UPX_FLAGS)" | grep -q -- '--force-macos'; then \
@if [ "$$(uname -s)" = "Darwin" ] && [ "$(LINK_FORCE_MACOS_UPX)" = "1" ] && ! printf '%s\n' "$(UPX_FLAGS)" | grep -q -- '--force-macos'; then \
echo "macOS UPX builds must pass --force-macos in UPX_FLAGS."; \
exit 1; \
fi
@@ -37,11 +37,11 @@ dev: deps
# Production build
build: deps
CGO_ENABLED=0 $(WAILS) build -trimpath -ldflags "-s -w -buildid= -X github.com/andydixon/nexus/internal/api.appVersion=$(VERSION) $(LD_UPDATE_URL)"
CGO_ENABLED=0 $(WAILS) build -trimpath -ldflags "-s -w -buildid= -X github.com/andydixon/link/internal/api.appVersion=$(VERSION) $(LD_UPDATE_URL)"
# Production build with UPX compression
build-compressed: deps upx-check
CGO_ENABLED=0 $(WAILS) build -trimpath -upx $(if $(UPX_FLAGS),-upxflags "$(UPX_FLAGS)",) -ldflags "-s -w -buildid= -X github.com/andydixon/nexus/internal/api.appVersion=$(VERSION) $(LD_UPDATE_URL)"
CGO_ENABLED=0 $(WAILS) build -trimpath -upx $(if $(UPX_FLAGS),-upxflags "$(UPX_FLAGS)",) -ldflags "-s -w -buildid= -X github.com/andydixon/link/internal/api.appVersion=$(VERSION) $(LD_UPDATE_URL)"
# Run tests
test:
@@ -74,7 +74,7 @@ release:
# Help
help:
@echo "Nexus Terminal — Build Targets"
@echo "Alchementary Link — Build Targets"
@echo ""
@echo " make deps — Install all dependencies"
@echo " make dev — Run in development mode with hot reload"
+3 -3
View File
@@ -1,8 +1,8 @@
# Nexus Terminal
# Alchementary Link
**A desktop client for SSH, Telnet, RAW TCP, VNC, GitHub, local terminals, and Leviathan binary analysis — with secure credential handling and a focused, multi-session workflow.**
Nexus Terminal helps you manage many connections at once, keep terminal work organised, and move files with confidence.
Alchementary Link helps you manage many connections at once, keep terminal work organised, and move files with confidence.
## Highlights
@@ -17,7 +17,7 @@ Nexus Terminal helps you manage many connections at once, keep terminal work org
## Quick start
1. Launch Nexus Terminal.
1. Launch Alchementary Link.
2. Click **New** in the sidebar and create a connection profile.
3. Click **Connect** to open a session in a new tab.
+15 -15
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# build-all.sh — Cross-build helper for Nexus Terminal (macOS/Linux hosts)
# build-all.sh — Cross-build helper for Alchementary Link (macOS/Linux hosts)
# Builds all supported targets where the required toolchains are installed.
# Usage: ./build-all.sh
set -euo pipefail
@@ -13,9 +13,9 @@ YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
log() { echo -e "${GREEN}[nexus]${NC} $1"; }
warn() { echo -e "${YELLOW}[nexus]${NC} $1"; }
err() { echo -e "${RED}[nexus]${NC} $1" >&2; }
log() { echo -e "${GREEN}[link]${NC} $1"; }
warn() { echo -e "${YELLOW}[link]${NC} $1"; }
err() { echo -e "${RED}[link]${NC} $1" >&2; }
MIN_GO_VERSION="1.25.12"
WAILS_VERSION="$(awk '$1 == "github.com/wailsapp/wails/v2" { print $2; exit }' go.mod)"
@@ -94,7 +94,7 @@ wails_cmd() {
}
ensure_upx() {
if ! nexus_bool_enabled "${NEXUS_COMPRESS:-0}"; then
if ! link_bool_enabled "${LINK_COMPRESS:-0}"; then
return
fi
if command -v upx >/dev/null 2>&1; then
@@ -132,9 +132,9 @@ build_version() {
build_ldflags() {
local version="$1"
local flags="-s -w -X github.com/andydixon/nexus/internal/api.appVersion=${version}"
if [ -n "${NEXUS_UPDATE_URL:-}" ]; then
flags="${flags} -X github.com/andydixon/nexus/internal/api.appUpdateURL=${NEXUS_UPDATE_URL}"
local flags="-s -w -X github.com/andydixon/link/internal/api.appVersion=${version}"
if [ -n "${LINK_UPDATE_URL:-}" ]; then
flags="${flags} -X github.com/andydixon/link/internal/api.appUpdateURL=${LINK_UPDATE_URL}"
fi
echo "${flags}"
}
@@ -186,24 +186,24 @@ build_target() {
local host_arch_value="$4"
local version="$5"
local platform="${target_os}/${target_arch}"
nexus_configure_pure_go_build
link_configure_pure_go_build
local ldflags
ldflags="$(build_ldflags "${version}")"
local tags
tags="$(build_tags)"
local build_args=("-platform" "$target_os/$target_arch" "-trimpath")
if nexus_use_upx_for_target "$target_os"; then
if link_use_upx_for_target "$target_os"; then
if ! command -v upx >/dev/null 2>&1; then
warn "Skipping ${platform} (NEXUS_COMPRESS=1 requires UPX)"
warn "Skipping ${platform} (LINK_COMPRESS=1 requires UPX)"
((SKIPPED_TARGETS += 1))
return 0
fi
build_args+=("-upx")
if [ -n "${NEXUS_UPX_FLAGS:-}" ]; then
build_args+=("-upxflags" "$NEXUS_UPX_FLAGS")
if [ -n "${LINK_UPX_FLAGS:-}" ]; then
build_args+=("-upxflags" "$LINK_UPX_FLAGS")
fi
elif nexus_skips_upx_for_target "$target_os"; then
warn "Skipping UPX compression for ${platform}; distribute the zipped .app instead, or set NEXUS_FORCE_MACOS_UPX=1 with suitable NEXUS_UPX_FLAGS."
elif link_skips_upx_for_target "$target_os"; then
warn "Skipping UPX compression for ${platform}; distribute the zipped .app instead, or set LINK_FORCE_MACOS_UPX=1 with suitable LINK_UPX_FLAGS."
fi
if [ -n "$tags" ]; then
build_args+=("-tags" "$tags")
+11 -11
View File
@@ -1,11 +1,11 @@
# build.ps1 — Windows build script for Nexus Terminal
# build.ps1 — Windows build script for Alchementary Link
# Usage: .\build.ps1 [dev|build|test|clean|deps]
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Write-Info($Message) { Write-Host "[nexus] $Message" -ForegroundColor Green }
function Write-Warn($Message) { Write-Host "[nexus] $Message" -ForegroundColor Yellow }
function Write-Err($Message) { Write-Host "[nexus] $Message" -ForegroundColor Red }
function Write-Info($Message) { Write-Host "[link] $Message" -ForegroundColor Green }
function Write-Warn($Message) { Write-Host "[link] $Message" -ForegroundColor Yellow }
function Write-Err($Message) { Write-Host "[link] $Message" -ForegroundColor Red }
function Test-BoolEnabled([string]$Value) {
return $Value -match '^(1|true|TRUE|yes|YES|on|ON)$'
@@ -67,9 +67,9 @@ function Get-BuildVersion {
}
function Get-Ldflags([string]$Version) {
$flags = "-s -w -buildid= -X github.com/andydixon/nexus/internal/api.appVersion=$Version"
if ($env:NEXUS_UPDATE_URL) {
$flags += " -X github.com/andydixon/nexus/internal/api.appUpdateURL=$($env:NEXUS_UPDATE_URL)"
$flags = "-s -w -buildid= -X github.com/andydixon/link/internal/api.appVersion=$Version"
if ($env:LINK_UPDATE_URL) {
$flags += " -X github.com/andydixon/link/internal/api.appUpdateURL=$($env:LINK_UPDATE_URL)"
}
return $flags
}
@@ -92,13 +92,13 @@ function Build {
Remove-Item Env:CGO_CXXFLAGS -ErrorAction SilentlyContinue
$buildArgs = @('build', '-trimpath')
if (Test-BoolEnabled $env:NEXUS_COMPRESS) {
if (Test-BoolEnabled $env:LINK_COMPRESS) {
if (-not (Get-Command upx -ErrorAction SilentlyContinue)) {
throw 'NEXUS_COMPRESS=1 requires UPX. Install it first.'
throw 'LINK_COMPRESS=1 requires UPX. Install it first.'
}
$buildArgs += '-upx'
if ($env:NEXUS_UPX_FLAGS) {
$buildArgs += @('-upxflags', $env:NEXUS_UPX_FLAGS)
if ($env:LINK_UPX_FLAGS) {
$buildArgs += @('-upxflags', $env:LINK_UPX_FLAGS)
}
}
$buildArgs += @('-ldflags', $ldflags)
+18 -18
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# build.sh — Build script for Nexus Terminal
# build.sh — Build script for Alchementary Link
# Provides a reproducible build process for CI and local builds.
# Usage: ./build.sh [dev|build|test|clean|deps]
set -euo pipefail
@@ -14,12 +14,12 @@ GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() { echo -e "${GREEN}[nexus]${NC} $1"; }
warn() { echo -e "${YELLOW}[nexus]${NC} $1"; }
err() { echo -e "${RED}[nexus]${NC} $1" >&2; }
log() { echo -e "${GREEN}[link]${NC} $1"; }
warn() { echo -e "${YELLOW}[link]${NC} $1"; }
err() { echo -e "${RED}[link]${NC} $1" >&2; }
MIN_GO_VERSION="1.25.12"
WAILS_VERSION="$(nexus_wails_version go.mod)"
WAILS_VERSION="$(link_wails_version go.mod)"
WAILS_BIN=""
WAILS_USE_GO_RUN=0
@@ -93,16 +93,16 @@ wails_cmd() {
}
ensure_upx() {
if ! nexus_bool_enabled "${NEXUS_COMPRESS:-0}"; then
if ! link_bool_enabled "${LINK_COMPRESS:-0}"; then
return
fi
if nexus_skips_upx_for_target "$(host_os)"; then
if link_skips_upx_for_target "$(host_os)"; then
return
fi
if command -v upx >/dev/null 2>&1; then
return
fi
err "NEXUS_COMPRESS=1 requires UPX. Install it first, for example: brew install upx"
err "LINK_COMPRESS=1 requires UPX. Install it first, for example: brew install upx"
exit 1
}
@@ -134,7 +134,7 @@ check_linux_deps() {
}
build_tags() {
nexus_build_tags
link_build_tags
}
host_os() {
@@ -146,11 +146,11 @@ host_os() {
}
build_version() {
nexus_build_version
link_build_version
}
build_ldflags() {
nexus_build_ldflags "$1"
link_build_ldflags "$1"
}
# Check prerequisites
@@ -200,16 +200,16 @@ build() {
version="$(build_version)"
local tags
tags="$(build_tags)"
nexus_configure_pure_go_build
link_configure_pure_go_build
local build_args=("-trimpath")
if nexus_bool_enabled "${NEXUS_COMPRESS:-0}"; then
if nexus_skips_upx_for_target "$(host_os)"; then
warn "Skipping UPX compression on macOS; distribute the zipped .app instead, or set NEXUS_FORCE_MACOS_UPX=1 with suitable NEXUS_UPX_FLAGS."
if link_bool_enabled "${LINK_COMPRESS:-0}"; then
if link_skips_upx_for_target "$(host_os)"; then
warn "Skipping UPX compression on macOS; distribute the zipped .app instead, or set LINK_FORCE_MACOS_UPX=1 with suitable LINK_UPX_FLAGS."
else
log "Using UPX compression"
build_args+=("-upx")
if [ -n "${NEXUS_UPX_FLAGS:-}" ]; then
build_args+=("-upxflags" "$NEXUS_UPX_FLAGS")
if [ -n "${LINK_UPX_FLAGS:-}" ]; then
build_args+=("-upxflags" "$LINK_UPX_FLAGS")
fi
fi
fi
@@ -222,7 +222,7 @@ build() {
# Replace Wails-generated icon with custom app icon on macOS.
if [[ "$OSTYPE" == darwin* ]]; then
local app_bundle="build/bin/nexus-terminal.app"
local app_bundle="build/bin/alchementary-link.app"
local custom_icns="build/darwin/AppIcon.icns"
if [ -d "$app_bundle" ] && [ -f "$custom_icns" ]; then
cp "$custom_icns" "$app_bundle/Contents/Resources/iconfile.icns"
@@ -100,10 +100,10 @@
<circle cx="198" cy="266" r="10" fill="#FFBD2E"/>
<circle cx="234" cy="266" r="10" fill="#28C840"/>
<!-- Title bar text: "nexus ssh" -->
<!-- Title bar text: "link ssh" -->
<text x="512" y="273" font-family="'SF Mono', 'Courier New', monospace"
font-size="20" fill="#8892A4" text-anchor="middle" dominant-baseline="middle"
letter-spacing="3">nexus — ssh</text>
letter-spacing="3">link — ssh</text>
<!-- Terminal separator line -->
<line x1="112" y1="292" x2="912" y2="292" stroke="#00D4FF" stroke-width="1" stroke-opacity="0.2"/>
@@ -111,7 +111,7 @@
<!-- MAIN TERMINAL CONTENT -->
<!-- Line 1: prompt line -->
<text x="152" y="360" font-family="'SF Mono', 'Courier New', monospace"
font-size="38" fill="#8892A4" filter="url(#softGlow)">user@nexus:~$</text>
font-size="38" fill="#8892A4" filter="url(#softGlow)">user@link:~$</text>
<!-- THE BIG PROMPT SYMBOL: ">_" — cyan glowing, centered -->
<text x="512" y="560" font-family="'SF Mono', 'Courier New', monospace"

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

+1 -1
View File
@@ -8,7 +8,7 @@
<key>CFBundleExecutable</key>
<string>{{.OutputFilename}}</string>
<key>CFBundleIdentifier</key>
<string>com.andydixon.nexusterminal</string>
<string>com.alchementary.link</string>
<key>CFBundleVersion</key>
<string>{{.Info.ProductVersion}}</string>
<key>CFBundleGetInfoString</key>

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

+10 -10
View File
@@ -1,4 +1,4 @@
// Command vscode-bridge exposes the Nexus backend to VS Code extensions over a
// Command vscode-bridge exposes the Link backend to VS Code extensions over a
// localhost-only JSON-RPC and event stream.
package main
@@ -22,14 +22,14 @@ import (
"syscall"
"time"
"github.com/andydixon/nexus/internal/api"
"github.com/andydixon/nexus/internal/audit"
"github.com/andydixon/nexus/internal/config"
"github.com/andydixon/nexus/internal/preferences"
"github.com/andydixon/nexus/internal/recording"
"github.com/andydixon/nexus/internal/secrets"
"github.com/andydixon/nexus/internal/sessions"
"github.com/andydixon/nexus/internal/storage"
"github.com/andydixon/link/internal/api"
"github.com/andydixon/link/internal/audit"
"github.com/andydixon/link/internal/config"
"github.com/andydixon/link/internal/preferences"
"github.com/andydixon/link/internal/recording"
"github.com/andydixon/link/internal/secrets"
"github.com/andydixon/link/internal/sessions"
"github.com/andydixon/link/internal/storage"
)
type rpcRequest struct {
@@ -160,7 +160,7 @@ func newBridgeApp() (*bridgeApp, error) {
}
secretStore := secrets.NewSecretStore(cfg.SecretPath)
keychainStore := secrets.NewKeychainStore("NexusTerminal")
keychainStore := secrets.NewKeychainStore("AlchementaryLink")
auditLog := audit.NewLogger(store)
prefs := preferences.NewManager(store)
if err := prefs.Load(); err != nil {
+1 -1
View File
@@ -69,7 +69,7 @@ checkout.
The release scripts disable CGO and use a pure-Go SQLite driver, so Linux and Windows cross-targets do not need C cross-compilers for the application backend. To build a smaller set, override the default targets:
```bash
NEXUS_RELEASE_TARGETS=linux/amd64,windows/amd64 ./release.sh
LINK_RELEASE_TARGETS=linux/amd64,windows/amd64 ./release.sh
```
## Platform caveats
+9 -9
View File
@@ -1,12 +1,12 @@
# Nexus Terminal Overview
# Alchementary Link Overview
Nexus Terminal is a desktop connection and analysis workspace for people who manage servers, network services, remote desktops, repositories, and binary artefacts every day. It brings SSH, Telnet, RAW TCP, VNC, GitHub, local shell, and Leviathan binary-analysis workflows into one focused app with secure credential handling, multi-session organisation, file transfer, automation, recording, and inspection tooling built in.
Alchementary Link is a desktop connection and analysis workspace for people who manage servers, network services, remote desktops, repositories, and binary artefacts every day. It brings SSH, Telnet, RAW TCP, VNC, GitHub, local shell, and Leviathan binary-analysis workflows into one focused app with secure credential handling, multi-session organisation, file transfer, automation, recording, and inspection tooling built in.
## Product positioning
Nexus is more than an SSH client. It is a single command centre for remote access, terminal operations, file movement, raw socket inspection, GitHub repository visibility, binary triage, and repeatable operational workflows.
Link is more than an SSH client. It is a single command centre for remote access, terminal operations, file movement, raw socket inspection, GitHub repository visibility, binary triage, and repeatable operational workflows.
Use Nexus when you need to:
Use Link when you need to:
- Connect quickly to many systems without re-entering credentials.
- Keep multiple live sessions organised with tabs, pinned tabs, split panes, and status indicators.
@@ -37,7 +37,7 @@ Use Nexus when you need to:
- Filter profiles by protocol, group, tag, and favourite status.
- Test basic host and port reachability before connecting.
- Import existing OpenSSH config entries from `~/.ssh/config`, including hostnames, usernames, ports, identity files, and jump hosts.
- Export and import profiles using encrypted `.nexus` bundles.
- Export and import profiles using encrypted `.link` bundles.
- Choose import behaviour for duplicates: skip, overwrite, or duplicate.
### Multi-session workspace
@@ -149,7 +149,7 @@ Use Nexus when you need to:
### VNC capabilities
- VNC remote desktop sessions.
- noVNC-based viewer inside the Nexus workspace.
- noVNC-based viewer inside the Link workspace.
- Password prompt and password storage modes.
- View-only mode.
- Scale-to-fit display.
@@ -173,7 +173,7 @@ Use Nexus when you need to:
### Leviathan binary workbench
- Switch from **Nexus Terminal** to **Leviathan** from the sidebar header.
- Switch from **Alchementary Link** to **Leviathan** from the sidebar header.
- Load a local file into a working document, inspect it with multiple tools, then save the patched working copy.
- Tools open as normal tabs beside connection sessions.
- Leviathan tabs close immediately without connection-disconnect confirmation.
@@ -271,7 +271,7 @@ Use Nexus when you need to:
### Suggested headline
Nexus Terminal: one desktop workspace for SSH, files, sockets, remote desktops, repositories, and binary artefacts.
Alchementary Link: one desktop workspace for SSH, files, sockets, remote desktops, repositories, and binary artefacts.
### Suggested subheadline
@@ -279,7 +279,7 @@ Manage SSH, Telnet, RAW TCP, VNC, GitHub, local shell, and Leviathan binary-anal
### Short product description
Nexus Terminal helps developers, operators, support engineers, reverse engineers, firmware analysts, and homelab users manage many connections and artefacts without juggling separate tools. Save profiles, organise them with groups and tags, connect in tabs or split panes, move files over SFTP, inspect raw TCP traffic, open VNC desktops, browse GitHub repositories, triage binaries with Leviathan, and keep credentials protected with keychain and encrypted-vault storage.
Alchementary Link helps developers, operators, support engineers, reverse engineers, firmware analysts, and homelab users manage many connections and artefacts without juggling separate tools. Save profiles, organise them with groups and tags, connect in tabs or split panes, move files over SFTP, inspect raw TCP traffic, open VNC desktops, browse GitHub repositories, triage binaries with Leviathan, and keep credentials protected with keychain and encrypted-vault storage.
### Product page feature bullets
+4 -4
View File
@@ -1,10 +1,10 @@
# Nexus Terminal Quick Start
# Alchementary Link Quick Start
Nexus Terminal is a desktop client for SSH, Telnet, RAW TCP, VNC, GitHub, local terminal sessions, and Leviathan binary analysis. This page is a shareable quick start for everyday use.
Alchementary Link is a desktop client for SSH, Telnet, RAW TCP, VNC, GitHub, local terminal sessions, and Leviathan binary analysis. This page is a shareable quick start for everyday use.
## Get connected in minutes
1. Open Nexus Terminal.
1. Open Alchementary Link.
2. Click **New** in the sidebar.
3. Choose a protocol and enter the connection details.
4. Click **Connect** to open the session in a new tab.
@@ -26,7 +26,7 @@ Create a **VNC** profile for remote desktop sessions. Use SSH tunnelling when po
## Leviathan binary tools
Click **Leviathan** beside **Nexus Terminal** in the sidebar header to switch from connection profiles to binary-analysis tools. Use **Load** to choose a file, then open tools such as Hex Editor, Overview, Hashes, Entropy, Strings, Carve, BinSnoop, Structures, PDF, SquashFS, Transforms, and Spectrogram tools. Tools open in normal tabs and can be closed without a disconnect confirmation.
Click **Leviathan** beside **Alchementary Link** in the sidebar header to switch from connection profiles to binary-analysis tools. Use **Load** to choose a file, then open tools such as Hex Editor, Overview, Hashes, Entropy, Strings, Carve, BinSnoop, Structures, PDF, SquashFS, Transforms, and Spectrogram tools. Tools open in normal tabs and can be closed without a disconnect confirmation.
## File transfers on SSH
+7 -7
View File
@@ -1,4 +1,4 @@
# Nexus Terminal User Manual
# Alchementary Link User Manual
## Contents
@@ -23,7 +23,7 @@
### Launch and connect
- Start Nexus Terminal.
- Start Alchementary Link.
- Click **New** in the sidebar, choose a protocol, and fill in the profile details.
- Click **Connect** to open a session in a new tab.
@@ -89,7 +89,7 @@ Tip: Use the search box to filter profiles by name, host, or tag.
### VNC
- Remote desktop viewer embedded in the Nexus workspace.
- Remote desktop viewer embedded in the Link workspace.
- Password prompt and saved-password modes.
- View-only mode, scale-to-fit, shared sessions, and quality controls.
- Optional SSH tunnel for safer VNC transport.
@@ -169,7 +169,7 @@ Screenshot: SFTP browser open alongside the terminal.
## 9. Drag-and-drop uploads and paste
When you drop file(s) into an active terminal, Nexus Terminal asks whether to:
When you drop file(s) into an active terminal, Alchementary Link asks whether to:
- **Upload** to a chosen remote directory, or
- **Paste contents** into the terminal as if typed.
@@ -205,11 +205,11 @@ Screenshot: Recordings list with playback controls.
## 11. Leviathan binary workbench
Leviathan is the integrated binary-analysis and editing workspace inside Nexus Terminal. It is useful for firmware images, unknown binaries, archives, captures, embedded files, suspicious documents, and audio spectrogram experiments.
Leviathan is the integrated binary-analysis and editing workspace inside Alchementary Link. It is useful for firmware images, unknown binaries, archives, captures, embedded files, suspicious documents, and audio spectrogram experiments.
### Switching to Leviathan
Click **Leviathan** in the top-left sidebar header. The normal connection list changes into a tool list. Click **Nexus Terminal** to return to saved servers and groups.
Click **Leviathan** in the top-left sidebar header. The normal connection list changes into a tool list. Click **Alchementary Link** to return to saved servers and groups.
Use **Load** to select a local file. The selected file becomes the current Leviathan working document. Use **Save** to write the patched working copy to a chosen destination.
@@ -267,7 +267,7 @@ Leviathan keeps loaded files file-backed where possible and writes patched worki
## 12. Security and credentials
Nexus Terminal stores credentials securely:
Alchementary Link stores credentials securely:
- **OS keychain first** (Keychain, Secret Service, or Credential Manager).
- **Encrypted vault** fallback using AES-256-GCM with Argon2id.
+3 -3
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Nexus Terminal</title>
<title>Alchementary Link</title>
<!-- Vendor styles -->
<link rel="stylesheet" href="/src/css/lib/xterm.css" />
@@ -52,10 +52,10 @@
<span class="sidebar-product-switch" aria-label="Application mode">
<button
class="sidebar-title sidebar-mode-toggle is-active"
id="mode-nexus"
id="mode-link"
type="button"
>
Nexus Terminal
<span class="sidebar-brand-gold">Alchementary</span>Link
</button>
<button
class="sidebar-title sidebar-mode-toggle"
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "nexus-terminal-frontend",
"name": "alchementary-link-frontend",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "nexus-terminal-frontend",
"name": "alchementary-link-frontend",
"version": "0.1.0",
"hasInstallScript": true,
"dependencies": {
+2 -2
View File
@@ -1,8 +1,8 @@
{
"name": "nexus-terminal-frontend",
"name": "alchementary-link-frontend",
"version": "0.1.0",
"private": true,
"description": "Frontend for Nexus Terminal desktop SSH client",
"description": "Frontend for Alchementary Link desktop SSH client",
"scripts": {
"install:assets": "node scripts/copy-assets.js",
"bundle:novnc": "node scripts/bundle-novnc.js",
+1 -1
View File
@@ -1 +1 @@
c004a0f41f9a4d08a1439b48dba5780a
24ecf1ed6af3b41cf2f6d8fa0bc732ec
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* build.js — Build script for the Nexus Terminal frontend.
* build.js — Build script for the Alchementary Link frontend.
*
* In production mode (no flags): copies vendor assets, assembles the
* dist/ directory with all files needed by the Wails embed, and validates
+2 -2
View File
@@ -8,7 +8,7 @@ const frontendRoot = path.resolve(__dirname, "..");
const source = fs.readFileSync(path.join(frontendRoot, "src/js/irc.js"), "utf8");
const context = {
window: {
NexusTerminal: {
AlchementaryLink: {
createElement: () => {
throw new Error("formatting tests must not create DOM elements");
},
@@ -18,7 +18,7 @@ const context = {
vm.createContext(context);
vm.runInContext(source, context, { filename: "src/js/irc.js" });
const formatting = context.window.NexusTerminal.IRCFormatting;
const formatting = context.window.AlchementaryLink.IRCFormatting;
assert.ok(formatting, "IRC formatting helpers should be exported");
assert.strictEqual(
+8 -8
View File
@@ -143,7 +143,7 @@ assertContains(
assertContains(
"frontend/index.html",
"mode-leviathan",
"Leviathan mode toggle must be present beside Nexus Terminal",
"Leviathan mode toggle must be present beside Alchementary Link",
);
assertContains(
"frontend/index.html",
@@ -232,12 +232,12 @@ assertContains(
);
assertContains(
"frontend/src/js/app.js",
"window.NexusTerminal.VNCViewer?.destroyViewer(sessionId)",
"window.AlchementaryLink.VNCViewer?.destroyViewer(sessionId)",
"session teardown must disconnect and remove VNC viewers",
);
assertContains(
"frontend/src/js/app.js",
"window.NexusTerminal.IRCViewer?.destroyViewer(sessionId)",
"window.AlchementaryLink.IRCViewer?.destroyViewer(sessionId)",
"session teardown must dispose IRC terminals and resize observers",
);
assertContains(
@@ -262,7 +262,7 @@ assertContains(
);
assertContains(
"frontend/src/js/splitpane.js",
"await window.NexusTerminal.wailsService.DisconnectSession(sessionInfo.id)",
"await window.AlchementaryLink.wailsService.DisconnectSession(sessionInfo.id)",
"late split-pane connection results must be disconnected after their pane closes",
);
assertContains(
@@ -404,7 +404,7 @@ assertContains(
assertContains(
"internal/api/leviathan.go",
"LeviathanRunTool",
"Nexus API must expose the imported Leviathan engine",
"Link API must expose the imported Leviathan engine",
);
assertNotContains(
"internal/api/leviathan.go",
@@ -528,12 +528,12 @@ assertNotContains(
);
assertContains(
"build.sh",
"NEXUS_COMPRESS",
"LINK_COMPRESS",
"local builds must expose an explicit compression switch",
);
assertContains(
"scripts/build-common.sh",
"nexus_use_upx_for_target",
"link_use_upx_for_target",
"build scripts must share one UPX target-selection helper",
);
assertContains(
@@ -563,7 +563,7 @@ assertContains(
);
assertContains(
"Makefile",
"NEXUS_FORCE_MACOS_UPX",
"LINK_FORCE_MACOS_UPX",
"Makefile compressed builds must make macOS UPX opt-in",
);
assertTreeNotContains(
+12 -12
View File
@@ -14,7 +14,7 @@ function loadScript(relativePath, additions = {}) {
Set,
setTimeout,
clearTimeout,
window: { NexusTerminal: {} },
window: { AlchementaryLink: {} },
document: {
querySelector: () => null,
addEventListener: () => {},
@@ -32,7 +32,7 @@ function loadScript(relativePath, additions = {}) {
async function testRepeatedSplitCleanup() {
const context = loadScript("src/js/splitpane.js");
const SplitPaneManager = context.window.NexusTerminal.SplitPaneManager;
const SplitPaneManager = context.window.AlchementaryLink.SplitPaneManager;
const closed = [];
const app = {
closeSessionImmediate: async (sessionId, options) => {
@@ -100,7 +100,7 @@ async function testRepeatedSplitCleanup() {
function testClosedPaneRejectsLateConnection() {
const context = loadScript("src/js/splitpane.js");
const SplitPaneManager = context.window.NexusTerminal.SplitPaneManager;
const SplitPaneManager = context.window.AlchementaryLink.SplitPaneManager;
const manager = new SplitPaneManager({ closeSessionImmediate: async () => {} });
const pane = {
dataset: { paneId: "pane-1" },
@@ -128,7 +128,7 @@ function testClosedPaneRejectsLateConnection() {
function testPerSessionStoresReleaseIDs() {
const stateContext = loadScript("src/js/state.js");
const State = stateContext.window.NexusTerminal.State;
const State = stateContext.window.AlchementaryLink.State;
const state = new State();
state.addSession({ id: "session-1" });
state.broadcastTargets = new Set(["session-1"]);
@@ -138,7 +138,7 @@ function testPerSessionStoresReleaseIDs() {
const recordingContext = loadScript("src/js/recordings.js");
const RecordingManager =
recordingContext.window.NexusTerminal.RecordingManager;
recordingContext.window.AlchementaryLink.RecordingManager;
const recordings = new RecordingManager({});
recordings.activeSessions.add("session-1");
recordings.cleanup("session-1");
@@ -149,7 +149,7 @@ async function testCompleteAppTeardownIsImmediateAndIdempotent() {
let finishBackendClose;
let backendCloses = 0;
const context = loadScript("src/js/app.js");
const App = context.window.NexusTerminal.App;
const App = context.window.AlchementaryLink.App;
const app = Object.create(App.prototype);
const resources = {
sftp: new Set(["session-1"]),
@@ -195,10 +195,10 @@ async function testCompleteAppTeardownIsImmediateAndIdempotent() {
};
app.updateStatusBar = () => {};
context.window.NexusTerminal.GitHubViewer = drop("github");
context.window.NexusTerminal.VNCViewer = drop("vnc");
context.window.NexusTerminal.IRCViewer = drop("irc");
context.window.NexusTerminal.wailsService = {
context.window.AlchementaryLink.GitHubViewer = drop("github");
context.window.AlchementaryLink.VNCViewer = drop("vnc");
context.window.AlchementaryLink.IRCViewer = drop("irc");
context.window.AlchementaryLink.wailsService = {
DisconnectSession: () => {
backendCloses++;
return new Promise((resolve) => {
@@ -240,7 +240,7 @@ async function testLatencyPingIsSingleFlightAndIgnoresStaleResults() {
addEventListener: () => {},
},
});
const App = context.window.NexusTerminal.App;
const App = context.window.AlchementaryLink.App;
const app = Object.create(App.prototype);
app._latencySessionID = null;
app._latencyTimer = null;
@@ -248,7 +248,7 @@ async function testLatencyPingIsSingleFlightAndIgnoresStaleResults() {
app._latencyPingInFlight = false;
const pending = [];
context.window.NexusTerminal.wailsService = {
context.window.AlchementaryLink.wailsService = {
PingSession: (sessionID) =>
new Promise((resolve, reject) => {
pending.push({ sessionID, resolve, reject });
+1 -1
View File
@@ -1,5 +1,5 @@
/* ==========================================================================
Nexus Terminal — Base / Reset
Alchementary Link — Base / Reset
Modern CSS reset and foundational element styles.
========================================================================== */
+1 -1
View File
@@ -1,5 +1,5 @@
/* ==========================================================================
Nexus Terminal — Reusable Components
Alchementary Link — Reusable Components
Buttons, form controls, badges, dropdowns, modals, notifications, etc.
========================================================================== */
+1 -1
View File
@@ -1,5 +1,5 @@
/* ==========================================================================
Nexus Terminal — Profile Editor & Form Polish
Alchementary Link — Profile Editor & Form Polish
Section headers, grouped fields, protocol-specific visibility,
inline validation, tag input, jump host list, key file selector.
========================================================================== */
+1 -1
View File
@@ -1,5 +1,5 @@
/* ==========================================================================
Nexus Terminal - IRC viewer
Alchementary Link - IRC viewer
========================================================================== */
.irc-viewer-panel {
+1 -1
View File
@@ -1,5 +1,5 @@
/* ==========================================================================
Nexus Terminal — Application Layout
Alchementary Link — Application Layout
Grid shell, sidebar, tab bar, workspace, and status bar structure.
========================================================================== */
+1 -1
View File
@@ -1,4 +1,4 @@
/* Leviathan mode inside the Nexus shell */
/* Leviathan mode inside the Link shell */
.leviathan-actions {
display: grid;
+1 -1
View File
@@ -1,5 +1,5 @@
/* ==========================================================================
Nexus Terminal — Modals & Drawers
Alchementary Link — Modals & Drawers
Profile editor drawer, settings modal, known hosts viewer,
confirmation dialogs, host key verification dialog.
========================================================================== */
+1 -1
View File
@@ -1,5 +1,5 @@
/* ==========================================================================
Nexus Terminal — SFTP File Browser
Alchementary Link — SFTP File Browser
Right-side panel for browsing remote files alongside the terminal.
========================================================================== */
+5 -1
View File
@@ -1,5 +1,5 @@
/* ==========================================================================
Nexus Terminal — Sidebar
Alchementary Link — Sidebar
Connection manager: logo, search, sections, profile list, footer.
========================================================================== */
@@ -74,6 +74,10 @@
color: var(--text-primary);
}
.sidebar-brand-gold {
color: var(--sidebar-group-title);
}
.sidebar-mode-toggle:hover {
color: var(--text-secondary);
}
+1 -1
View File
@@ -1,5 +1,5 @@
/* ==========================================================================
Nexus Terminal — Split Pane Layout
Alchementary Link — Split Pane Layout
Draggable split panes within session panels for multi-terminal views.
========================================================================== */
+1 -1
View File
@@ -1,5 +1,5 @@
/* ==========================================================================
Nexus Terminal — Tab Bar & Tabs
Alchementary Link — Tab Bar & Tabs
Compact session tabs with status indicators, protocol badges, and
close / pin controls.
========================================================================== */
+1 -1
View File
@@ -1,5 +1,5 @@
/* ==========================================================================
Nexus Terminal — Terminal Integration
Alchementary Link — Terminal Integration
xterm.js container, header info strip, RAW mode views, and send bar.
========================================================================== */
+1 -1
View File
@@ -1,5 +1,5 @@
/* ==========================================================================
Nexus Terminal — Design Tokens
Alchementary Link — Design Tokens
Central palette, spacing, typography, and layout constants.
All values consumed via var(--token-name) throughout the application.
========================================================================== */
+56 -56
View File
@@ -1,6 +1,6 @@
(function () {
/**
* Nexus Terminal — main application entry point.
* Alchementary Link — main application entry point.
* Initialises the application shell, loads profiles, and sets up event listeners.
*/
@@ -24,7 +24,7 @@
TriggerEngine,
LeviathanWorkbench,
stateLabel,
} = window.NexusTerminal;
} = window.AlchementaryLink;
class App {
constructor() {
@@ -35,7 +35,7 @@
this.terminals = new TerminalManager(this);
this.profileEditor = new ProfileEditor(this);
this.rawViewer = new RawViewer(this);
window.NexusTerminal.IRCViewer?.setApp(this);
window.AlchementaryLink.IRCViewer?.setApp(this);
this.settings = new Settings(this);
this.commandPalette = new CommandPalette(this);
this.secretPrompt = new SecretPrompt(this);
@@ -54,7 +54,7 @@
this._latencyGeneration = 0;
this._latencyPingInFlight = false;
this.updateInfo = null;
this.mode = "nexus";
this.mode = "link";
}
/**
@@ -92,10 +92,10 @@
const start = Date.now();
const check = () => {
const hasBridge =
typeof window.NexusTerminal?.hasWailsService === "function"
? window.NexusTerminal.hasWailsService()
: Boolean(window.NexusTerminal?.wailsService && window.runtime);
if (window.NexusTerminal?.wailsService && hasBridge) {
typeof window.AlchementaryLink?.hasWailsService === "function"
? window.AlchementaryLink.hasWailsService()
: Boolean(window.AlchementaryLink?.wailsService && window.runtime);
if (window.AlchementaryLink?.wailsService && hasBridge) {
resolve();
} else if (Date.now() - start < maxWait) {
setTimeout(check, 50);
@@ -108,7 +108,7 @@
resolve();
}
};
Promise.resolve(window.NexusTerminal?.wailsServiceReady)
Promise.resolve(window.AlchementaryLink?.wailsServiceReady)
.catch(() => null)
.finally(check);
});
@@ -121,7 +121,7 @@
*/
async loadProfiles() {
try {
const profiles = await window.NexusTerminal.wailsService.ListProfiles();
const profiles = await window.AlchementaryLink.wailsService.ListProfiles();
this.state.setProfiles(profiles || []);
this.sidebar.render();
} catch (err) {
@@ -134,7 +134,7 @@
*/
async loadPreferences() {
try {
const prefs = await window.NexusTerminal.wailsService.GetPreferences();
const prefs = await window.AlchementaryLink.wailsService.GetPreferences();
this.state.setPreferences(prefs || {});
this._applyPreferences();
} catch (err) {
@@ -149,10 +149,10 @@
async checkSecretStore() {
try {
const initialised =
await window.NexusTerminal.wailsService.IsSecretStoreInitialised();
await window.AlchementaryLink.wailsService.IsSecretStoreInitialised();
this.state.setSecretStoreInitialised(!!initialised);
if (initialised) {
const unlocked = await window.NexusTerminal.wailsService.IsSecretStoreUnlocked();
const unlocked = await window.AlchementaryLink.wailsService.IsSecretStoreUnlocked();
this.state.setSecretStoreState(!!unlocked);
} else {
this.state.setSecretStoreState(false);
@@ -240,7 +240,7 @@
if (entry) {
const { cols, rows } = entry.term;
try {
window.NexusTerminal.wailsService.ResizeSession(
window.AlchementaryLink.wailsService.ResizeSession(
payload.sessionID,
cols,
rows,
@@ -256,7 +256,7 @@
setTimeout(fit, 50);
setTimeout(fit, 300);
setTimeout(fit, 800);
setTimeout(() => window.NexusTerminal.IRCViewer?.fit(payload.sessionID), 50);
setTimeout(() => window.AlchementaryLink.IRCViewer?.fit(payload.sessionID), 50);
}
});
@@ -275,7 +275,7 @@
// GitHub repository data
window.runtime.EventsOn("session:github:repos", (payload) => {
window.NexusTerminal.GitHubViewer.updateRepos(
window.AlchementaryLink.GitHubViewer.updateRepos(
payload.sessionID,
payload.login,
payload.repos,
@@ -284,7 +284,7 @@
// GitHub refresh errors
window.runtime.EventsOn("session:github:error", (payload) => {
window.NexusTerminal.GitHubViewer.showError(
window.AlchementaryLink.GitHubViewer.showError(
payload.sessionID,
payload.error,
);
@@ -292,7 +292,7 @@
// GitHub status updates
window.runtime.EventsOn("session:github:status", (payload) => {
window.NexusTerminal.GitHubViewer.setStatus(
window.AlchementaryLink.GitHubViewer.setStatus(
payload.sessionID,
payload.status,
);
@@ -300,7 +300,7 @@
// VNC WebSocket proxy ready — connect the viewer
window.runtime.EventsOn("session:vnc:ready", (payload) => {
window.NexusTerminal.VNCViewer.connect(
window.AlchementaryLink.VNCViewer.connect(
payload.sessionID,
payload.wsURL,
);
@@ -308,7 +308,7 @@
// IRC events
window.runtime.EventsOn("session:irc:event", (payload) => {
window.NexusTerminal.IRCViewer.handleEvent(payload);
window.AlchementaryLink.IRCViewer.handleEvent(payload);
this.tabs.markActivity(payload.sessionID);
});
@@ -462,9 +462,9 @@
() => this.recordings.cleanup(sessionId),
() => this.terminals.destroyTerminal(sessionId),
() => this.rawViewer.destroyViewer(sessionId),
() => window.NexusTerminal.GitHubViewer?.destroyViewer(sessionId),
() => window.NexusTerminal.VNCViewer?.destroyViewer(sessionId),
() => window.NexusTerminal.IRCViewer?.destroyViewer(sessionId),
() => window.AlchementaryLink.GitHubViewer?.destroyViewer(sessionId),
() => window.AlchementaryLink.VNCViewer?.destroyViewer(sessionId),
() => window.AlchementaryLink.IRCViewer?.destroyViewer(sessionId),
() => this.leviathan?.destroyPanel(sessionId),
];
for (const cleanup of localCleanups) {
@@ -486,7 +486,7 @@
let backendCleanup = Promise.resolve();
try {
backendCleanup = Promise.resolve(
window.NexusTerminal.wailsService.DisconnectSession(sessionId),
window.AlchementaryLink.wailsService.DisconnectSession(sessionId),
);
} catch (_) {
// Ignore failures; all local resources have already been released.
@@ -646,7 +646,7 @@
this._bindClick("btn-recordings", () => this.recordings.openBrowser());
this._bindClick("btn-new-tab", () => this.profileEditor.openNew());
this._bindClick("btn-broadcast", () => this.toggleBroadcast());
this._bindClick("mode-nexus", () => this.setMode("nexus"));
this._bindClick("mode-link", () => this.setMode("link"));
this._bindClick("mode-leviathan", () => this.setMode("leviathan"));
// Wire the "Local Machine" sidebar button
@@ -679,7 +679,7 @@
}
// Window resize → re-fit active terminal (debounced)
const { debounce } = window.NexusTerminal;
const { debounce } = window.AlchementaryLink;
window.addEventListener(
"resize",
debounce(() => {
@@ -698,21 +698,21 @@
// ── App mode switching ─────────────────────────────────────────
/**
* Switches the sidebar between Nexus profiles and Leviathan tools.
* @param {"nexus"|"leviathan"} mode
* Switches the sidebar between Link profiles and Leviathan tools.
* @param {"link"|"leviathan"} mode
*/
setMode(mode) {
const leviathanEnabled =
this.state.preferences?.leviathanEnabled === true;
this.mode =
mode === "leviathan" && leviathanEnabled ? "leviathan" : "nexus";
mode === "leviathan" && leviathanEnabled ? "leviathan" : "link";
const root = document.getElementById("app");
if (root) {
root.classList.toggle("mode-leviathan", this.mode === "leviathan");
}
document
.getElementById("mode-nexus")
?.classList.toggle("is-active", this.mode === "nexus");
.getElementById("mode-link")
?.classList.toggle("is-active", this.mode === "link");
document
.getElementById("mode-leviathan")
?.classList.toggle("is-active", this.mode === "leviathan");
@@ -769,7 +769,7 @@
* Opens a modal to select which sessions receive broadcast input.
*/
selectBroadcastTargets() {
const { createElement, escapeHtml } = window.NexusTerminal;
const { createElement, escapeHtml } = window.AlchementaryLink;
const sessions = this.state
.getAllSessions()
.filter(
@@ -925,7 +925,7 @@
try {
const sessionInfo =
await window.NexusTerminal.wailsService.ConnectSession(profileId);
await window.AlchementaryLink.wailsService.ConnectSession(profileId);
this.state.addSession(sessionInfo);
this.tabs.addTab(sessionInfo);
@@ -936,7 +936,7 @@
} else if (profile.protocol === "github") {
const container = document.getElementById(panelId);
if (container) {
window.NexusTerminal.GitHubViewer.createViewer(
window.AlchementaryLink.GitHubViewer.createViewer(
sessionInfo.id,
container,
profile.displayName || profile.host,
@@ -945,7 +945,7 @@
} else if (profile.protocol === "vnc") {
const container = document.getElementById(panelId);
if (container) {
window.NexusTerminal.VNCViewer.createViewer(
window.AlchementaryLink.VNCViewer.createViewer(
sessionInfo.id,
container,
profile,
@@ -954,7 +954,7 @@
} else if (profile.protocol === "irc") {
const container = document.getElementById(panelId);
if (container) {
window.NexusTerminal.IRCViewer.createViewer(
window.AlchementaryLink.IRCViewer.createViewer(
sessionInfo.id,
container,
profile,
@@ -977,7 +977,7 @@
*/
async connectLocal() {
try {
const sessionInfo = await window.NexusTerminal.wailsService.ConnectLocal();
const sessionInfo = await window.AlchementaryLink.wailsService.ConnectLocal();
this.state.addSession(sessionInfo);
this.tabs.addTab(sessionInfo);
@@ -1000,7 +1000,7 @@
* Opens a quick connect dialog for ad-hoc connections without saving a profile.
*/
async quickConnect() {
const createElement = window.NexusTerminal.createElement;
const createElement = window.AlchementaryLink.createElement;
const result = await new Promise((resolve) => {
const backdrop = createElement("div", "modal-backdrop");
const modal = createElement("div", "modal modal-sm");
@@ -1033,7 +1033,7 @@
</div>
<div class="form-group qc-nick-group" style="display:none">
<label class="form-label">Nick</label>
<input class="form-input qc-nick" type="text" value="nexus" />
<input class="form-input qc-nick" type="text" value="link" />
</div>
</div>
<div class="modal-footer">
@@ -1115,12 +1115,12 @@
};
if (result.protocol === "irc") {
profile.irc = {
nick: result.nick || "nexus",
realName: "Nexus Terminal",
nick: result.nick || "link",
realName: "Alchementary Link",
autoJoinRooms: [],
};
}
const sessionInfo = await window.NexusTerminal.wailsService.ConnectQuick(profile);
const sessionInfo = await window.AlchementaryLink.wailsService.ConnectQuick(profile);
this.state.addSession(sessionInfo);
this.tabs.addTab(sessionInfo);
@@ -1133,7 +1133,7 @@
} else if (result.protocol === "irc") {
const container = document.getElementById(panelId);
if (container) {
window.NexusTerminal.IRCViewer.createViewer(
window.AlchementaryLink.IRCViewer.createViewer(
sessionInfo.id,
container,
profile,
@@ -1159,8 +1159,8 @@
* @private
*/
_confirmReuseSession(profile, session) {
const createElement = window.NexusTerminal.createElement;
const label = window.NexusTerminal.escapeHtml(
const createElement = window.AlchementaryLink.createElement;
const label = window.AlchementaryLink.escapeHtml(
(profile.displayName || profile.host || "Untitled").trim(),
);
const stateLabel =
@@ -1240,7 +1240,7 @@
* @returns {Promise<boolean>}
*/
confirmDirectVNCProfile(profile, action) {
const { createElement, createModalShell } = window.NexusTerminal;
const { createElement, createModalShell } = window.AlchementaryLink;
const label = (
profile.displayName ||
profile.vnc?.vncHost ||
@@ -1391,7 +1391,7 @@
const doPing = async () => {
if (!isCurrent()) return;
if (!window.NexusTerminal?.wailsService) {
if (!window.AlchementaryLink?.wailsService) {
scheduleNext();
return;
}
@@ -1399,7 +1399,7 @@
this._latencyPingInFlight = true;
let succeeded = false;
try {
const ms = await window.NexusTerminal.wailsService.PingSession(sessionID);
const ms = await window.AlchementaryLink.wailsService.PingSession(sessionID);
if (!isCurrent()) return;
succeeded = true;
const el = document.getElementById("status-latency");
@@ -1443,12 +1443,12 @@
}
async checkForUpdates(silent) {
if (!window.NexusTerminal?.wailsService) {
if (!window.AlchementaryLink?.wailsService) {
return;
}
try {
const info = await window.NexusTerminal.wailsService.CheckForUpdates();
const info = await window.AlchementaryLink.wailsService.CheckForUpdates();
this.setUpdateInfo(info);
if (!silent && info && info.available) {
this.notifications.info(
@@ -1508,7 +1508,7 @@
leviathanButton.hidden = !leviathanEnabled;
}
if (!leviathanEnabled && this.mode === "leviathan") {
this.setMode("nexus");
this.setMode("link");
}
// Refit any visible terminal after geometry changes.
@@ -1600,10 +1600,10 @@
* @private
*/
_showGuidedErrorModal(title, label, message, onAction, actions) {
const createElement = window.NexusTerminal.createElement;
const createElement = window.AlchementaryLink.createElement;
const backdrop = createElement("div", "modal-backdrop");
const modal = createElement("div", "modal modal-sm");
const safeLabel = window.NexusTerminal.escapeHtml(label || "Session");
const safeLabel = window.AlchementaryLink.escapeHtml(label || "Session");
modal.innerHTML = `
<div class="modal-header">
@@ -1615,7 +1615,7 @@
<strong>${safeLabel}</strong>
</p>
<p style="font-size:var(--text-sm);color:var(--text-secondary);white-space:pre-line;">
${window.NexusTerminal.escapeHtml(message)}
${window.AlchementaryLink.escapeHtml(message)}
</p>
</div>
<div class="modal-footer"></div>
@@ -1888,7 +1888,7 @@
* @private
*/
_inputPrompt(title, label, placeholder) {
const createElement = window.NexusTerminal.createElement;
const createElement = window.AlchementaryLink.createElement;
return new Promise((resolve) => {
const backdrop = createElement("div", "modal-backdrop");
const modal = createElement("div", "modal modal-sm");
@@ -1943,7 +1943,7 @@
// ── Bootstrap ────────────────────────────────────────────────────────
window.NexusTerminal.App = App;
window.AlchementaryLink.App = App;
document.addEventListener("DOMContentLoaded", () => {
try {
+6 -6
View File
@@ -1,11 +1,11 @@
(function () {
/**
* Nexus Terminal — command palette (Ctrl+K).
* Alchementary Link — command palette (Ctrl+K).
* Provides a searchable overlay for quick access to application commands,
* profile connections, and utility actions.
*/
const { createElement, escapeHtml, fuzzyMatch, $ } = window.NexusTerminal;
const { createElement, escapeHtml, fuzzyMatch, $ } = window.AlchementaryLink;
class CommandPalette {
/**
@@ -172,7 +172,7 @@
section: "Security",
action: async () => {
try {
await window.NexusTerminal.wailsService.LockSecretStore();
await window.AlchementaryLink.wailsService.LockSecretStore();
this.app.state.setSecretStoreState(false);
this.app.notifications.info("Secret store locked");
} catch (err) {
@@ -189,7 +189,7 @@
try {
const pw =
await this.app.secretPrompt.promptMasterPassword(false);
await window.NexusTerminal.wailsService.UnlockSecretStore(pw);
await window.AlchementaryLink.wailsService.UnlockSecretStore(pw);
this.app.state.setSecretStoreState(true);
this.app.notifications.success("Secret store unlocked");
} catch (err) {
@@ -441,6 +441,6 @@
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.CommandPalette = CommandPalette;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.CommandPalette = CommandPalette;
})();
+6 -6
View File
@@ -1,12 +1,12 @@
(function () {
/**
* Nexus Terminal — GitHub repository viewer.
* Alchementary Link — GitHub repository viewer.
* Renders a scrollable list of repositories grouped by owner/organisation,
* sorted by last commit date (newest first). Automatically refreshes via
* Wails events emitted by the Go backend.
*/
const { createElement, escapeHtml, relativeTime, $ } = window.NexusTerminal;
const { createElement, escapeHtml, relativeTime, $ } = window.AlchementaryLink;
class GitHubViewer {
constructor() {
@@ -425,7 +425,7 @@
this._statusEl.textContent = `Downloading ${repo.fullName}...`;
try {
await window.NexusTerminal.wailsService.DownloadGitHubRepoArchive(
await window.AlchementaryLink.wailsService.DownloadGitHubRepoArchive(
this.sessionID,
repo.fullName,
ref,
@@ -441,7 +441,7 @@
if (!this._statusEl) return;
this._statusEl.textContent = "Refreshing…";
try {
await window.NexusTerminal.wailsService.RefreshGitHubRepos(this.sessionID);
await window.AlchementaryLink.wailsService.RefreshGitHubRepos(this.sessionID);
} catch (err) {
this.showError(err.message || "Refresh failed");
}
@@ -492,6 +492,6 @@
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.GitHubViewer = new GitHubViewer();
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.GitHubViewer = new GitHubViewer();
})();
+11 -11
View File
@@ -1,13 +1,13 @@
(function () {
/**
* Nexus Terminal - IRC viewer.
* Alchementary Link - IRC viewer.
* Uses xterm.js as the chat stream, with room subtabs, a composer, and
* room/user sidebars.
*/
/* global Terminal, FitAddon, SearchAddon, WebLinksAddon */
const { createElement } = window.NexusTerminal;
const { createElement } = window.AlchementaryLink;
const SERVER_TARGET = "server";
const MAX_LINES_PER_TARGET = 2000;
const MAX_LISTED_ROOMS = 200;
@@ -415,7 +415,7 @@
const room = joinInput.value.trim();
if (!room) return;
try {
await window.NexusTerminal.wailsService.IRCJoin(this.sessionID, room);
await window.AlchementaryLink.wailsService.IRCJoin(this.sessionID, room);
joinInput.value = "";
} catch (err) {
this._notifyError("Join failed: " + err);
@@ -439,7 +439,7 @@
this.roomList = [];
this._renderRoomList("Loading rooms...");
try {
await window.NexusTerminal.wailsService.IRCListRooms(this.sessionID);
await window.AlchementaryLink.wailsService.IRCListRooms(this.sessionID);
} catch (err) {
this._renderRoomList("Room list failed");
this._notifyError("Room list failed: " + err);
@@ -476,7 +476,7 @@
const themeName =
(this.app?.state?.preferences || {}).terminalTheme || "charcoal";
this.term = new Terminal({
theme: window.NexusTerminal.getTerminalTheme(themeName),
theme: window.AlchementaryLink.getTerminalTheme(themeName),
fontFamily:
this.profile.terminalFontFamily ||
'JetBrains Mono, Menlo, Monaco, Consolas, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", monospace',
@@ -668,7 +668,7 @@
if (!text.trim()) return;
this.inputEl.value = "";
try {
await window.NexusTerminal.wailsService.IRCHandleInput(
await window.AlchementaryLink.wailsService.IRCHandleInput(
this.sessionID,
this.activeTarget,
text,
@@ -686,7 +686,7 @@
async _joinRoom(room) {
if (!room) return;
try {
await window.NexusTerminal.wailsService.IRCJoin(this.sessionID, room);
await window.AlchementaryLink.wailsService.IRCJoin(this.sessionID, room);
} catch (err) {
this._notifyError("Join failed: " + err);
}
@@ -694,7 +694,7 @@
async _refreshJoinedRooms() {
try {
const rooms = await window.NexusTerminal.wailsService.IRCRooms(this.sessionID);
const rooms = await window.AlchementaryLink.wailsService.IRCRooms(this.sessionID);
if (this.destroyed) return;
this.handleEvent({ type: "rooms", rooms: Array.isArray(rooms) ? rooms : [] });
} catch (_) {
@@ -785,11 +785,11 @@
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.IRCFormatting = Object.freeze({
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.IRCFormatting = Object.freeze({
formatEvent: formatIRCEvent,
formatText: formatIRCText,
plainText: plainIRCText,
});
window.NexusTerminal.IRCViewer = new IRCViewer();
window.AlchementaryLink.IRCViewer = new IRCViewer();
})();
+21 -21
View File
@@ -1,6 +1,6 @@
(function () {
const { createElement, escapeHtml, formatBytes, generateId } =
window.NexusTerminal;
window.AlchementaryLink;
const tools = [
{
@@ -116,7 +116,7 @@
async loadFile() {
try {
const payload = await window.NexusTerminal.wailsService.LeviathanOpenFile();
const payload = await window.AlchementaryLink.wailsService.LeviathanOpenFile();
this.applyState(payload);
this.refreshOpenHexTabs();
} catch (err) {
@@ -127,9 +127,9 @@
async saveFile() {
try {
const path = await window.NexusTerminal.wailsService.LeviathanSaveWorking();
const path = await window.AlchementaryLink.wailsService.LeviathanSaveWorking();
if (path) this.app.notifications.success("Saved working copy");
const state = await window.NexusTerminal.wailsService.LeviathanState();
const state = await window.AlchementaryLink.wailsService.LeviathanState();
this.applyState(state);
} catch (err) {
this.app.notifications.error("Save failed: " + err);
@@ -231,7 +231,7 @@
const load = async () => {
try {
status.textContent = "Loading hex...";
const result = await window.NexusTerminal.wailsService.LeviathanRunTool("hex", {
const result = await window.AlchementaryLink.wailsService.LeviathanRunTool("hex", {
start: Number(start.input.value || 0),
bytes: Number(bytes.input.value || 2048),
width: Number(width.input.value || 16),
@@ -288,7 +288,7 @@
length: Number(length.input.value || -1),
max: Number(max.input.value || 1000),
};
const out = await window.NexusTerminal.wailsService.LeviathanRunTool("search", payload);
const out = await window.AlchementaryLink.wailsService.LeviathanRunTool("search", payload);
this.lastResults.set(sessionId, out);
this._renderResult(summary, results, out, "search");
} catch (err) {
@@ -304,7 +304,7 @@
const out = this.lastResults.get(sessionId);
if (!out) return;
try {
await window.NexusTerminal.wailsService.LeviathanExportJSON("search.json", out);
await window.AlchementaryLink.wailsService.LeviathanExportJSON("search.json", out);
this.app.notifications.success("Exported JSON");
} catch (err) {
this.app.notifications.error("Export failed: " + err);
@@ -339,7 +339,7 @@
let selectedImagePath = "";
chooseImage.addEventListener("click", async () => {
try {
const path = await window.NexusTerminal.wailsService.SelectFile("Select Image to Insert", "");
const path = await window.AlchementaryLink.wailsService.SelectFile("Select Image to Insert", "");
if (!path) return;
selectedImagePath = path;
imagePath.textContent = path;
@@ -369,7 +369,7 @@
if (!selectedImagePath) throw new Error("Select an image to insert first.");
payload.imagePath = selectedImagePath;
}
const out = await window.NexusTerminal.wailsService.LeviathanRunTool(toolId, payload);
const out = await window.AlchementaryLink.wailsService.LeviathanRunTool(toolId, payload);
this.lastResults.set(sessionId, out);
summary.innerHTML = "";
results.innerHTML = "";
@@ -416,7 +416,7 @@
results.appendChild(createElement("div", "leviathan-empty", { textContent: "Rendering spectrogram..." }));
const payload = {};
for (const field of fields) payload[field.key] = this._fieldValue(field);
const out = await window.NexusTerminal.wailsService.LeviathanRunTool("spectrogram-view", payload);
const out = await window.AlchementaryLink.wailsService.LeviathanRunTool("spectrogram-view", payload);
this.lastResults.set(sessionId, out);
summary.innerHTML = "";
results.innerHTML = "";
@@ -442,7 +442,7 @@
const out = this.lastResults.get(sessionId);
if (!out) return;
try {
await window.NexusTerminal.wailsService.LeviathanExportJSON("spectrogram-view.json", out);
await window.AlchementaryLink.wailsService.LeviathanExportJSON("spectrogram-view.json", out);
this.app.notifications.success("Exported JSON");
} catch (err) {
this.app.notifications.error("Export failed: " + err);
@@ -480,7 +480,7 @@
running = true;
try {
if (tool.id === "diff" && !this.state?.compare) {
const compare = await window.NexusTerminal.wailsService.LeviathanOpenCompare();
const compare = await window.AlchementaryLink.wailsService.LeviathanOpenCompare();
this.applyState(compare);
}
results.innerHTML = "";
@@ -489,7 +489,7 @@
for (const field of fields) {
payload[field.key] = this._fieldValue(field);
}
const out = await window.NexusTerminal.wailsService.LeviathanRunTool(tool.id, payload);
const out = await window.AlchementaryLink.wailsService.LeviathanRunTool(tool.id, payload);
this.lastResults.set(sessionId, out);
this._renderResult(summary, results, out, tool.id);
if (out && out.state) this.applyState(out.state);
@@ -505,7 +505,7 @@
const out = this.lastResults.get(sessionId);
if (!out) return;
try {
await window.NexusTerminal.wailsService.LeviathanExportJSON(`${tool.id}.json`, out);
await window.AlchementaryLink.wailsService.LeviathanExportJSON(`${tool.id}.json`, out);
this.app.notifications.success("Exported JSON");
} catch (err) {
this.app.notifications.error("Export failed: " + err);
@@ -697,7 +697,7 @@
const offset = Number(cell.dataset.offset || 0);
cell.dataset.pendingHex = hex;
cell.classList.add("is-saving");
window.NexusTerminal.wailsService
window.AlchementaryLink.wailsService
.LeviathanSetByte(offset, hex)
.then((payload) => {
if (cell.dataset.pendingHex === hex) {
@@ -1125,7 +1125,7 @@
}
const name = typeof nameBuilder === "function" ? nameBuilder(row) : this._foundFileName(row, "found");
try {
const path = await window.NexusTerminal.wailsService.LeviathanExportRange(name, range.offset, range.length);
const path = await window.AlchementaryLink.wailsService.LeviathanExportRange(name, range.offset, range.length);
if (path) this.app.notifications.success("Exported " + path);
} catch (err) {
this.app.notifications.error("Export failed: " + err);
@@ -1164,7 +1164,7 @@
}
_stateAction(method, sessionId) {
return window.NexusTerminal.wailsService[method]()
return window.AlchementaryLink.wailsService[method]()
.then((state) => {
this.applyState(state);
const session = this.app.state.getSession(sessionId);
@@ -1299,7 +1299,7 @@
const search = async () => {
try {
status.textContent = "Searching...";
const out = await window.NexusTerminal.wailsService.LeviathanRunTool("search", {
const out = await window.AlchementaryLink.wailsService.LeviathanRunTool("search", {
mode: mode.input.value,
query: query.input.value,
start: 0,
@@ -1435,7 +1435,7 @@
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.LeviathanWorkbench = LeviathanWorkbench;
window.NexusTerminal.LeviathanTools = tools;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.LeviathanWorkbench = LeviathanWorkbench;
window.AlchementaryLink.LeviathanTools = tools;
})();
+4 -4
View File
@@ -1,11 +1,11 @@
(function() {
/**
* Nexus Terminal — toast notification system.
* Alchementary Link — toast notification system.
* Provides non-blocking, auto-dismissing notifications that stack
* in the top-right corner of the viewport.
*/
const { createElement, escapeHtml } = window.NexusTerminal;
const { createElement, escapeHtml } = window.AlchementaryLink;
/** Small SVG icons for each notification type (16×16 viewBox). */
const ICONS = {
@@ -95,7 +95,7 @@ class Notifications {
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.Notifications = Notifications;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.Notifications = Notifications;
})();
+33 -33
View File
@@ -1,11 +1,11 @@
(function () {
/**
* Nexus Terminal — connection profile editor.
* Alchementary Link — connection profile editor.
* Renders a slide-out drawer for creating and editing connection profiles.
* Dynamically switches form sections based on the selected protocol.
*/
const { createElement, escapeHtml, generateId, $ } = window.NexusTerminal;
const { createElement, escapeHtml, generateId, $ } = window.AlchementaryLink;
/**
* Default values mirroring the Go backend defaults.
@@ -90,8 +90,8 @@
port: 6667,
protocol: "irc",
irc: {
nick: "nexus",
realName: "Nexus Terminal",
nick: "link",
realName: "Alchementary Link",
useTLS: false,
serverPasswordRequired: false,
serverPasswordMode: "ask-every-time",
@@ -467,10 +467,10 @@
_loadSSHKeyOptions() {
if (this._sshKeyOptionsLoading || this._sshKeyOptionsLoaded) return;
if (!window.NexusTerminal?.wailsService) return;
if (!window.AlchementaryLink?.wailsService) return;
this._sshKeyOptionsLoading = true;
window.NexusTerminal.wailsService.ListSSHKeys()
window.AlchementaryLink.wailsService.ListSSHKeys()
.then((keys) => {
this._sshKeyOptions = Array.isArray(keys) ? keys : [];
this._sshKeyOptionsLoaded = true;
@@ -1424,7 +1424,7 @@
const profileId = profile.id;
if (profileId) {
try {
const code = await window.NexusTerminal.wailsService.GetTOTPCode(profileId);
const code = await window.AlchementaryLink.wailsService.GetTOTPCode(profileId);
testBtn.textContent = code;
setTimeout(() => {
testBtn.textContent = "Test";
@@ -1454,7 +1454,7 @@
const profileId = profile.id;
const secret = secretInput.value.trim();
if (!profileId) {
NexusTerminal.Notifications.show(
AlchementaryLink.Notifications.show(
"Save the profile first, then set the TOTP secret.",
"warning",
);
@@ -1463,10 +1463,10 @@
if (!secret) {
// Delete existing secret
try {
await window.NexusTerminal.wailsService.DeleteTOTPSecret(profileId);
NexusTerminal.Notifications.show("TOTP secret removed.", "info");
await window.AlchementaryLink.wailsService.DeleteTOTPSecret(profileId);
AlchementaryLink.Notifications.show("TOTP secret removed.", "info");
} catch (err) {
NexusTerminal.Notifications.show(
AlchementaryLink.Notifications.show(
"Failed to remove TOTP secret.",
"error",
);
@@ -1474,15 +1474,15 @@
return;
}
try {
await window.NexusTerminal.wailsService.SetTOTPSecret(profileId, secret);
NexusTerminal.Notifications.show(
await window.AlchementaryLink.wailsService.SetTOTPSecret(profileId, secret);
AlchementaryLink.Notifications.show(
"TOTP secret saved securely.",
"success",
);
secretInput.value = "";
secretInput.placeholder = "••••••••• (secret saved)";
} catch (err) {
NexusTerminal.Notifications.show(
AlchementaryLink.Notifications.show(
"Failed to save TOTP secret: " + err,
"error",
);
@@ -1504,12 +1504,12 @@
const profileId = profile.id;
if (!profileId) return;
try {
await window.NexusTerminal.wailsService.DeleteTOTPSecret(profileId);
NexusTerminal.Notifications.show("TOTP secret removed.", "info");
await window.AlchementaryLink.wailsService.DeleteTOTPSecret(profileId);
AlchementaryLink.Notifications.show("TOTP secret removed.", "info");
secretInput.placeholder =
"Base32-encoded secret (e.g. from authenticator app setup)";
} catch (err) {
NexusTerminal.Notifications.show(
AlchementaryLink.Notifications.show(
"Failed to remove TOTP secret.",
"error",
);
@@ -1528,7 +1528,7 @@
if (profile.id) {
(async () => {
try {
const has = await window.NexusTerminal.wailsService.HasTOTPSecret(profile.id);
const has = await window.AlchementaryLink.wailsService.HasTOTPSecret(profile.id);
if (has) {
secretInput.placeholder = "••••••••• (secret saved)";
}
@@ -1972,8 +1972,8 @@
});
browseBtn.addEventListener("mousedown", (e) => e.preventDefault());
browseBtn.addEventListener("click", () => {
if (!window.NexusTerminal?.wailsService) return;
window.NexusTerminal.wailsService.SelectFile("Select SSH Private Key", "*")
if (!window.AlchementaryLink?.wailsService) return;
window.AlchementaryLink.wailsService.SelectFile("Select SSH Private Key", "*")
.then((path) => {
if (path) input.value = path;
})
@@ -2040,8 +2040,8 @@
browseBtn.addEventListener("mousedown", (e) => e.preventDefault());
browseBtn.addEventListener("click", () => {
if (!window.NexusTerminal?.wailsService) return;
window.NexusTerminal.wailsService.SelectFile("Select SSH Private Key", "*")
if (!window.AlchementaryLink?.wailsService) return;
window.AlchementaryLink.wailsService.SelectFile("Select SSH Private Key", "*")
.then((path) => {
if (path) keyInput.value = path;
this._validateKeyPath(keyInput, error);
@@ -2117,10 +2117,10 @@
}
return;
}
if (!window.NexusTerminal?.wailsService) {
if (!window.AlchementaryLink?.wailsService) {
return;
}
window.NexusTerminal.wailsService.FileExists(value)
window.AlchementaryLink.wailsService.FileExists(value)
.then((exists) => {
if (!exists) {
errorEl.textContent = "Key file not found.";
@@ -2131,7 +2131,7 @@
}
return;
}
return window.NexusTerminal.wailsService.FileIsPrivateKey(value).then((isKey) => {
return window.AlchementaryLink.wailsService.FileIsPrivateKey(value).then((isKey) => {
if (!isKey) {
errorEl.textContent = "File is not a recognised private key.";
errorEl.style.display = "block";
@@ -2488,8 +2488,8 @@
// IRC-specific
if (p.protocol === "irc") {
if (!p.irc) p.irc = {};
p.irc.nick = val("irc.nick") || "nexus";
p.irc.realName = val("irc.realName") || "Nexus Terminal";
p.irc.nick = val("irc.nick") || "link";
p.irc.realName = val("irc.realName") || "Alchementary Link";
p.irc.useTLS = val("irc.useTLS") || false;
p.irc.serverPasswordRequired =
val("irc.serverPasswordRequired") || false;
@@ -2584,11 +2584,11 @@
try {
let saved;
if (this._isNew) {
saved = await window.NexusTerminal.wailsService.CreateProfile(this._profile);
saved = await window.AlchementaryLink.wailsService.CreateProfile(this._profile);
this.app.state.addProfile(saved);
this.app.notifications.success("Profile created");
} else {
saved = await window.NexusTerminal.wailsService.UpdateProfile(this._profile);
saved = await window.AlchementaryLink.wailsService.UpdateProfile(this._profile);
this.app.state.updateProfile(saved);
this.app.notifications.success("Profile updated");
}
@@ -2609,7 +2609,7 @@
if (!ok) return;
try {
await window.NexusTerminal.wailsService.DeleteProfile(this._profile.id);
await window.AlchementaryLink.wailsService.DeleteProfile(this._profile.id);
this.app.state.removeProfile(this._profile.id);
this.app.sidebar.render();
this.app.notifications.success("Profile deleted");
@@ -2633,7 +2633,7 @@
this.app.notifications.info(`Testing connection to ${host}:${port}…`);
try {
const result = await window.NexusTerminal.wailsService.TestConnection(host, port);
const result = await window.AlchementaryLink.wailsService.TestConnection(host, port);
this.app.notifications.success(
`Reachable — ${result.latencyMs}ms latency`,
);
@@ -2764,6 +2764,6 @@
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.ProfileEditor = ProfileEditor;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.ProfileEditor = ProfileEditor;
})();
+7 -7
View File
@@ -1,12 +1,12 @@
(function () {
/**
* Nexus Terminal — RAW TCP data viewer.
* Alchementary Link — RAW TCP data viewer.
* Provides text, hex, and split display modes for raw socket data,
* along with a send bar, pause/resume, and capture saving.
*/
const { createElement, escapeHtml, formatTimestamp, formatBytes } =
window.NexusTerminal;
window.AlchementaryLink;
/**
* Maximum number of data entries retained in the scrollback buffer.
@@ -214,7 +214,7 @@
return `[${ts}] [${dir}] ${e.data}`;
});
const content = lines.join("\n");
await window.NexusTerminal.wailsService.SaveSessionCapture(sessionId, content);
await window.AlchementaryLink.wailsService.SaveSessionCapture(sessionId, content);
this.app.notifications.success("Capture saved");
} catch (err) {
this.app.notifications.error("Save failed: " + err);
@@ -256,8 +256,8 @@
const b64 =
format === "hex" || format === "escaped"
? btoa(dataToSend)
: window.NexusTerminal.utf8ToBase64(dataToSend);
await window.NexusTerminal.wailsService.WriteSession(sessionId, b64);
: window.AlchementaryLink.utf8ToBase64(dataToSend);
await window.AlchementaryLink.wailsService.WriteSession(sessionId, b64);
input.value = "";
// Also show in viewer as sent data
@@ -620,6 +620,6 @@
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.RawViewer = RawViewer;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.RawViewer = RawViewer;
})();
+17 -17
View File
@@ -1,12 +1,12 @@
(function() {
/**
* Nexus Terminal — Session Recording & Playback.
* Alchementary Link — Session Recording & Playback.
* Provides controls for starting/stopping recordings on active sessions,
* listing saved recordings, and a terminal-based playback viewer with
* speed controls and a progress scrubber.
*/
const { createElement, formatDuration } = window.NexusTerminal;
const { createElement, formatDuration } = window.AlchementaryLink;
// ---------------------------------------------------------------------------
// Recording Manager
@@ -41,7 +41,7 @@ class RecordingManager {
const cols = entry ? entry.term.cols : 80;
const rows = entry ? entry.term.rows : 24;
await window.NexusTerminal.wailsService.StartRecording(sessionId, cols, rows);
await window.AlchementaryLink.wailsService.StartRecording(sessionId, cols, rows);
this.activeSessions.add(sessionId);
this._updateRecordingIndicator(sessionId, true);
this.app.notifications.success('Recording started');
@@ -56,7 +56,7 @@ class RecordingManager {
*/
async stop(sessionId) {
try {
await window.NexusTerminal.wailsService.StopRecording(sessionId);
await window.AlchementaryLink.wailsService.StopRecording(sessionId);
this.activeSessions.delete(sessionId);
this._updateRecordingIndicator(sessionId, false);
this.app.notifications.success('Recording saved');
@@ -81,8 +81,8 @@ class RecordingManager {
async openBrowser() {
try {
const [recordings, storageInfo] = await Promise.all([
window.NexusTerminal.wailsService.ListRecordings(),
window.NexusTerminal.wailsService.GetRecordingStorageInfo(),
window.AlchementaryLink.wailsService.ListRecordings(),
window.AlchementaryLink.wailsService.GetRecordingStorageInfo(),
]);
this._showBrowserModal(recordings || [], storageInfo || {});
} catch (err) {
@@ -96,7 +96,7 @@ class RecordingManager {
*/
async playback(id) {
try {
const data = await window.NexusTerminal.wailsService.GetRecording(id);
const data = await window.AlchementaryLink.wailsService.GetRecording(id);
this._showPlaybackModal(data);
} catch (err) {
this.app.notifications.error('Failed to load recording: ' + err);
@@ -109,7 +109,7 @@ class RecordingManager {
*/
async deleteRecording(id) {
try {
await window.NexusTerminal.wailsService.DeleteRecording(id);
await window.AlchementaryLink.wailsService.DeleteRecording(id);
this.app.notifications.success('Recording deleted');
} catch (err) {
this.app.notifications.error('Failed to delete recording: ' + err);
@@ -235,11 +235,11 @@ class RecordingManager {
status.textContent = '';
btn.disabled = true;
try {
const result = await window.NexusTerminal.wailsService.CleanupRecordings(ageDays, maxBytes);
const result = await window.AlchementaryLink.wailsService.CleanupRecordings(ageDays, maxBytes);
status.textContent = `Deleted ${result.deleted || 0}; freed ${_formatBytes(result.freedBytes || 0)}.`;
const fresh = await window.NexusTerminal.wailsService.ListRecordings();
const fresh = await window.AlchementaryLink.wailsService.ListRecordings();
close();
this._showBrowserModal(fresh || [], await window.NexusTerminal.wailsService.GetRecordingStorageInfo());
this._showBrowserModal(fresh || [], await window.AlchementaryLink.wailsService.GetRecordingStorageInfo());
} catch (err) {
status.textContent = 'Cleanup failed: ' + err;
} finally {
@@ -314,7 +314,7 @@ class RecordingManager {
// Create xterm instance for playback.
const termContainer = modal.querySelector('#playback-terminal');
const { getTerminalTheme } = window.NexusTerminal;
const { getTerminalTheme } = window.AlchementaryLink;
const themeName = (this.app.state.preferences && this.app.state.preferences.terminalTheme) || 'charcoal';
const theme = getTerminalTheme ? getTerminalTheme(themeName) : {};
@@ -566,7 +566,7 @@ class ExportImportUI {
}
try {
const path = await window.NexusTerminal.wailsService.ExportProfilesEncrypted(selectedIds, password);
const path = await window.AlchementaryLink.wailsService.ExportProfilesEncrypted(selectedIds, password);
this.app.notifications.success(`Exported ${selectedIds.length} profile(s) to ${path}`);
close();
} catch (err) {
@@ -638,7 +638,7 @@ class ExportImportUI {
try {
confirmBtn.disabled = true;
confirmBtn.textContent = 'Importing...';
const count = await window.NexusTerminal.wailsService.ImportProfilesEncrypted(password, strategy);
const count = await window.AlchementaryLink.wailsService.ImportProfilesEncrypted(password, strategy);
this.app.notifications.success(`Imported ${count} profile(s)`);
await this.app.loadProfiles();
close();
@@ -694,8 +694,8 @@ function _formatBytes(bytes) {
// Export to global namespace
// ---------------------------------------------------------------------------
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.RecordingManager = RecordingManager;
window.NexusTerminal.ExportImportUI = ExportImportUI;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.RecordingManager = RecordingManager;
window.AlchementaryLink.ExportImportUI = ExportImportUI;
})();
+4 -4
View File
@@ -1,11 +1,11 @@
(function() {
/**
* Nexus Terminal — secret/passphrase prompt dialogs.
* Alchementary Link — secret/passphrase prompt dialogs.
* Provides modal prompts for collecting passwords, master-vault passphrases,
* and SSH host-key verification confirmations.
*/
const { createElement, escapeHtml } = window.NexusTerminal;
const { createElement, escapeHtml } = window.AlchementaryLink;
class SecretPrompt {
constructor(app) {
@@ -367,7 +367,7 @@ function _measureStrength(pw) {
return { percent: score, level: 'strong', label: 'Strong' };
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.SecretPrompt = SecretPrompt;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.SecretPrompt = SecretPrompt;
})();
+39 -39
View File
@@ -1,12 +1,12 @@
(function () {
/**
* Nexus Terminal — settings modal.
* Alchementary Link — settings modal.
* Renders a tabbed settings dialog for application-wide preferences,
* terminal defaults, security, known-hosts management, and about info.
*/
const { createElement, escapeHtml, formatTimestamp, $ } =
window.NexusTerminal;
window.AlchementaryLink;
/** Settings tab definitions. */
const TABS = [
@@ -65,13 +65,13 @@
this._activeTab = tabKey || this._activeTab || "general";
try {
this._knownHosts = (await window.NexusTerminal.wailsService.ListKnownHosts()) || [];
this._knownHosts = (await window.AlchementaryLink.wailsService.ListKnownHosts()) || [];
} catch (_) {
this._knownHosts = [];
}
try {
this._defaultShell = await window.NexusTerminal.wailsService.GetDefaultShell();
this._defaultShell = await window.AlchementaryLink.wailsService.GetDefaultShell();
} catch (_) {
this._defaultShell = "";
}
@@ -106,7 +106,7 @@
this._collectValues();
try {
await window.NexusTerminal.wailsService.SavePreferences(this._prefs);
await window.AlchementaryLink.wailsService.SavePreferences(this._prefs);
this.app.state.setPreferences(this._prefs);
// Apply terminal colour theme change immediately.
@@ -331,7 +331,7 @@
});
browseBtn.addEventListener("click", async () => {
try {
const path = await window.NexusTerminal.wailsService.BrowseForShell();
const path = await window.AlchementaryLink.wailsService.BrowseForShell();
if (path) {
shellInput.value = path;
}
@@ -356,7 +356,7 @@
const p = this._prefs;
// Terminal colour theme selector
const themes = window.NexusTerminal.listTerminalThemes();
const themes = window.AlchementaryLink.listTerminalThemes();
body.appendChild(
this._settingField(
"select",
@@ -448,7 +448,7 @@
initBtn.addEventListener("click", async () => {
try {
const pw = await this.app.secretPrompt.promptMasterPassword(true);
await window.NexusTerminal.wailsService.InitialiseSecretStore(pw);
await window.AlchementaryLink.wailsService.InitialiseSecretStore(pw);
this.app.state.setSecretStoreInitialised(true);
this.app.state.setSecretStoreState(true);
this.app.notifications.success("Secret store created");
@@ -467,7 +467,7 @@
unlockBtn.addEventListener("click", async () => {
try {
const pw = await this.app.secretPrompt.promptMasterPassword(false);
await window.NexusTerminal.wailsService.UnlockSecretStore(pw);
await window.AlchementaryLink.wailsService.UnlockSecretStore(pw);
this.app.state.setSecretStoreState(true);
this.app.notifications.success("Secret store unlocked");
this._renderTabContent(body);
@@ -484,7 +484,7 @@
});
lockBtn.addEventListener("click", async () => {
try {
await window.NexusTerminal.wailsService.LockSecretStore();
await window.AlchementaryLink.wailsService.LockSecretStore();
this.app.state.setSecretStoreState(false);
this.app.notifications.info("Secret store locked");
this._renderTabContent(body);
@@ -541,7 +541,7 @@
});
deleteBtn.addEventListener("click", async () => {
try {
await window.NexusTerminal.wailsService.DeleteKnownHost(kh.id);
await window.AlchementaryLink.wailsService.DeleteKnownHost(kh.id);
this._knownHosts = this._knownHosts.filter((h) => h.id !== kh.id);
this._renderTabContent(body);
this.app.notifications.success("Known host removed");
@@ -568,7 +568,7 @@
id: "getting-started",
title: "Getting Started",
content: `
<p>Welcome to <strong>Nexus Terminal</strong> — a desktop application for SSH, Telnet, RAW TCP, VNC, GitHub, local terminal, and Leviathan binary-analysis workflows.</p>
<p>Welcome to <strong>Alchementary Link</strong> — a desktop application for SSH, Telnet, RAW TCP, VNC, GitHub, local terminal, and Leviathan binary-analysis workflows.</p>
<h4>Creating Your First Connection</h4>
<ol>
<li>Click the <strong>+</strong> button at the top of the sidebar, or press <kbd>⌘N</kbd> (Mac) / <kbd>Ctrl+N</kbd> (Windows/Linux).</li>
@@ -578,7 +578,7 @@
<li>Click <strong>Save</strong>, then double-click the entry in the sidebar to connect.</li>
</ol>
<h4>Leviathan Tools</h4>
<p>Click <strong>Leviathan</strong> beside <strong>Nexus Terminal</strong> in the sidebar header to switch from connection profiles to binary-analysis tools. Use <strong>Load</strong> to choose a local file, then open tools such as Hex Editor, Overview, Entropy, Carve, BinSnoop, Structures, and Spectrogram View.</p>
<p>Click <strong>Leviathan</strong> beside <strong>Alchementary Link</strong> in the sidebar header to switch from connection profiles to binary-analysis tools. Use <strong>Load</strong> to choose a local file, then open tools such as Hex Editor, Overview, Entropy, Carve, BinSnoop, Structures, and Spectrogram View.</p>
<h4>Quick Connect</h4>
<p>Need to connect quickly without saving a profile? Press <kbd>⌘⇧Q</kbd> / <kbd>Ctrl+Shift+Q</kbd> to open the quick connect dialog. Choose SSH, Telnet, or RAW, enter the host and port, and you're connected immediately.</p>
<h4>Managing Connections</h4>
@@ -589,7 +589,7 @@
id: "ssh",
title: "SSH Connections",
content: `
<p>SSH (Secure Shell) is the most common way to connect to remote servers. Nexus Terminal supports:</p>
<p>SSH (Secure Shell) is the most common way to connect to remote servers. Alchementary Link supports:</p>
<ul>
<li><strong>Password authentication</strong> — enter your password when prompted, or save it in the encrypted vault.</li>
<li><strong>Public key authentication</strong> — point to your private key file (e.g. <code>~/.ssh/id_rsa</code>). If the key has a passphrase, you will be prompted.</li>
@@ -597,13 +597,13 @@
<li><strong>SSH agent</strong> — tick "Use SSH Agent" to authenticate via your system's SSH agent (e.g. <code>ssh-agent</code> or 1Password).</li>
</ul>
<h4>TOTP / 2FA Auto-Fill</h4>
<p>If your server requires a time-based one-time password (TOTP) during login, you can store the TOTP secret in the profile editor under the <strong>TOTP / 2FA</strong> section. When connecting, Nexus Terminal will detect 2FA prompts (such as "Verification code" or "OTP") and automatically generate and submit a 6-digit code. Use the <strong>Test</strong> button to verify your secret generates valid codes before connecting.</p>
<p>If your server requires a time-based one-time password (TOTP) during login, you can store the TOTP secret in the profile editor under the <strong>TOTP / 2FA</strong> section. When connecting, Alchementary Link will detect 2FA prompts (such as "Verification code" or "OTP") and automatically generate and submit a 6-digit code. Use the <strong>Test</strong> button to verify your secret generates valid codes before connecting.</p>
<h4>Jump Hosts (Bastions)</h4>
<p>If you need to reach a server through an intermediate host, add one or more jump hosts in the connection editor. Nexus Terminal will tunnel through each hop automatically.</p>
<p>If you need to reach a server through an intermediate host, add one or more jump hosts in the connection editor. Alchementary Link will tunnel through each hop automatically.</p>
<h4>Port Forwarding</h4>
<p>You can set up <strong>local</strong>, <strong>remote</strong>, and <strong>dynamic</strong> (SOCKS proxy) port forwards in the connection editor. These start automatically when the session connects.</p>
<h4>Proxy Support</h4>
<p>If you need to connect through a proxy, open the <strong>Proxy</strong> section in the profile editor. Nexus Terminal supports <strong>SOCKS5</strong> and <strong>HTTP CONNECT</strong> proxies, with optional username/password authentication. The proxy is used to reach the SSH server (or the first jump host if jump hosts are configured).</p>
<p>If you need to connect through a proxy, open the <strong>Proxy</strong> section in the profile editor. Alchementary Link supports <strong>SOCKS5</strong> and <strong>HTTP CONNECT</strong> proxies, with optional username/password authentication. The proxy is used to reach the SSH server (or the first jump host if jump hosts are configured).</p>
<h4>SFTP File Browser</h4>
<p>Once connected to an SSH server, click the <strong>📁</strong> icon on the tab to open the SFTP file browser. You can browse directories, upload, download, rename, and delete files from a side panel.</p>
<h4>SSH Agent Forwarding</h4>
@@ -627,7 +627,7 @@
content: `
<p>Telnet is an older, unencrypted protocol still used for network equipment, legacy systems, and some fun internet services.</p>
<ul>
<li><strong>Option negotiation</strong> — Nexus Terminal handles standard Telnet options (echo, terminal type, window size) automatically.</li>
<li><strong>Option negotiation</strong> — Alchementary Link handles standard Telnet options (echo, terminal type, window size) automatically.</li>
<li><strong>Character encoding</strong> — choose UTF-8, ASCII, or Latin-1 for the connection.</li>
<li><strong>Local echo</strong> — enable this if the remote host does not echo back what you type.</li>
<li><strong>Line mode</strong> — send data line-by-line (press Enter to send) rather than character-by-character.</li>
@@ -656,7 +656,7 @@
id: "vnc",
title: "VNC Connections",
content: `
<p>VNC profiles open a remote desktop viewer inside the Nexus workspace.</p>
<p>VNC profiles open a remote desktop viewer inside the Link workspace.</p>
<ul>
<li><strong>Password handling</strong> — ask each time, use the session only, or save the password in the encrypted secret store.</li>
<li><strong>View-only mode</strong> — observe a desktop without sending mouse or keyboard input.</li>
@@ -672,7 +672,7 @@
id: "github",
title: "GitHub Connections",
content: `
<p>The GitHub connection type lets you browse your repositories without leaving Nexus Terminal.</p>
<p>The GitHub connection type lets you browse your repositories without leaving Alchementary Link.</p>
<h4>Setting Up</h4>
<ol>
<li>Create a new connection and choose <strong>GitHub</strong> as the protocol.</li>
@@ -775,20 +775,20 @@
title: "Security & Credentials",
content: `
<h4>How Your Passwords Are Stored</h4>
<p>Nexus Terminal uses a <strong>two-tier</strong> approach to keep your credentials safe:</p>
<p>Alchementary Link uses a <strong>two-tier</strong> approach to keep your credentials safe:</p>
<ol>
<li><strong>OS keychain</strong> (preferred) — your operating system's built-in credential store (macOS Keychain, Windows Credential Manager, or Linux Secret Service). If available, this is used automatically.</li>
<li><strong>Encrypted vault</strong> (fallback) — if the OS keychain is not available, Nexus Terminal creates a local vault encrypted with AES-256-GCM. You set a master password to unlock it.</li>
<li><strong>Encrypted vault</strong> (fallback) — if the OS keychain is not available, Alchementary Link creates a local vault encrypted with AES-256-GCM. You set a master password to unlock it.</li>
</ol>
<h4>Encryption Details</h4>
<p>The vault uses <strong>Argon2id</strong> to derive the encryption key from your master password (128 MiB memory, making brute-force attacks very expensive). Each secret is individually encrypted and never stored in plain text.</p>
<h4>TOTP / 2FA Secrets</h4>
<p>TOTP secrets for SSH two-factor authentication are stored alongside your other credentials — encrypted in the OS keychain or the file vault. They are never saved in the profile database in plain text.</p>
<h4>Known Hosts</h4>
<p>When you connect to an SSH server for the first time, Nexus Terminal records the server's fingerprint. If the fingerprint changes on a subsequent connection, you'll be warned — this protects against man-in-the-middle attacks.</p>
<p>When you connect to an SSH server for the first time, Alchementary Link records the server's fingerprint. If the fingerprint changes on a subsequent connection, you'll be warned — this protects against man-in-the-middle attacks.</p>
<p>You can manage known hosts and change the verification policy in <strong>Settings → Known Hosts</strong>.</p>
<h4>Encrypted Export</h4>
<p>When exporting profiles, you can choose encrypted export (.nexus file) which protects the file with a password. This uses the same strong AES-256-GCM encryption as the vault.</p>
<p>When exporting profiles, you can choose encrypted export (.link file) which protects the file with a password. This uses the same strong AES-256-GCM encryption as the vault.</p>
`,
},
{
@@ -822,11 +822,11 @@
title: "Import & Export",
content: `
<h4>Exporting Profiles</h4>
<p>Click the <strong>↗</strong> export button in the sidebar footer. You can choose which profiles to include, and set a password to encrypt the file. Exported files use the <code>.nexus</code> extension.</p>
<p>Click the <strong>↗</strong> export button in the sidebar footer. You can choose which profiles to include, and set a password to encrypt the file. Exported files use the <code>.link</code> extension.</p>
<h4>Importing Profiles</h4>
<p>Click the <strong>↙</strong> import button. Select a <code>.nexus</code> file and enter the password used when exporting. Imported profiles will appear in your sidebar immediately.</p>
<p>Click the <strong>↙</strong> import button. Select a <code>.link</code> file and enter the password used when exporting. Imported profiles will appear in your sidebar immediately.</p>
<h4>Importing from OpenSSH</h4>
<p>If you have an existing <code>~/.ssh/config</code> file, go to <strong>Settings → About</strong> and click <strong>Import ~/.ssh/config</strong>. Nexus Terminal will create profiles from your SSH Host blocks, including hostnames, usernames, ports, identity files, and jump hosts.</p>
<p>If you have an existing <code>~/.ssh/config</code> file, go to <strong>Settings → About</strong> and click <strong>Import ~/.ssh/config</strong>. Alchementary Link will create profiles from your SSH Host blocks, including hostnames, usernames, ports, identity files, and jump hosts.</p>
`,
},
{
@@ -892,16 +892,16 @@
<h4>SFTP panel doesn't open</h4>
<p>SFTP is only available for <strong>SSH</strong> connections (not Telnet, RAW, or GitHub). Make sure you are connected to an SSH session first, then click the <strong>📁</strong> icon on the tab.</p>
<h4>The application looks blurry on my display</h4>
<p>Make sure your system display scaling is set correctly. On macOS, Nexus Terminal is fully Retina-aware. On Windows, check your DPI settings in Display preferences.</p>
<p>Make sure your system display scaling is set correctly. On macOS, Alchementary Link is fully Retina-aware. On Windows, check your DPI settings in Display preferences.</p>
<h4>Export/import isn't working</h4>
<p>Ensure you're using the same password to import that was used to export. The encrypted <code>.nexus</code> files cannot be opened with the wrong password.</p>
<p>Ensure you're using the same password to import that was used to export. The encrypted <code>.link</code> files cannot be opened with the wrong password.</p>
`,
},
{
id: "fun",
title: "Fun Things to Try",
content: `
<p>Nexus Terminal isn't just for work! Here are some entertaining things you can connect to:</p>
<p>Alchementary Link isn't just for work! Here are some entertaining things you can connect to:</p>
<h4>🌟 Star Wars in ASCII Art</h4>
<p>Create a <strong>Telnet</strong> connection to <code>towel.blinkenlights.nl</code> on port <strong>23</strong>. Sit back and enjoy the original Star Wars film retold entirely in ASCII art.</p>
<h4>🗺️ MapSCII — World Map in Your Terminal</h4>
@@ -974,9 +974,9 @@
_renderAboutTab(body) {
body.innerHTML = `
<div class="about-section">
<h3 class="about-title">Nexus Terminal</h3>
<h3 class="about-title">Alchementary Link</h3>
<p class="about-version" data-role="app-version">Version …</p>
<p class="about-desc">Developed by Andy Dixon - www.dixon.cx/nexus</p>
<p class="about-desc">Developed by Andy Dixon - alchementary.co.uk</p>
<div class="about-links">
<p>AES-256-GCM encrypted credential vault</p>
<p>Platform-native keychain integration</p>
@@ -993,8 +993,8 @@
const versionEl = body.querySelector("[data-role='app-version']");
if (versionEl) {
if (window.NexusTerminal?.wailsService) {
window.NexusTerminal.wailsService.GetAppInfo()
if (window.AlchementaryLink?.wailsService) {
window.AlchementaryLink.wailsService.GetAppInfo()
.then((info) => {
const version = (info && info.version) || "dev";
versionEl.textContent = `Version ${version}`;
@@ -1013,7 +1013,7 @@
updateBtn.disabled = true;
updateBtn.textContent = "Checking…";
try {
const info = await window.NexusTerminal.wailsService.CheckForUpdates();
const info = await window.AlchementaryLink.wailsService.CheckForUpdates();
const resultEl = body.querySelector("#settings-update-result");
if (this.app && typeof this.app.setUpdateInfo === "function") {
this.app.setUpdateInfo(info);
@@ -1061,7 +1061,7 @@
importBtn.disabled = true;
importBtn.textContent = "Importing…";
try {
const count = await window.NexusTerminal.wailsService.ImportSSHConfig(
const count = await window.AlchementaryLink.wailsService.ImportSSHConfig(
"",
"SSH Config",
);
@@ -1129,7 +1129,7 @@
};
try {
await window.NexusTerminal.wailsService.SavePreferences(prefs);
await window.AlchementaryLink.wailsService.SavePreferences(prefs);
this._prefs.leviathanEnabled = enabled;
this.app.state.setPreferences(prefs);
this.app._applyPreferences?.();
@@ -1271,6 +1271,6 @@
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.Settings = Settings;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.Settings = Settings;
})();
+13 -13
View File
@@ -1,6 +1,6 @@
(function () {
/**
* Nexus Terminal — SFTP File Browser
* Alchementary Link — SFTP File Browser
*
* Provides a right-side panel for browsing, uploading, downloading,
* renaming and deleting remote files alongside the terminal.
@@ -12,7 +12,7 @@
formatBytes,
relativeTime,
dismissContextMenus,
} = window.NexusTerminal;
} = window.AlchementaryLink;
/* ── Helpers ──────────────────────────────────────────────────────── */
@@ -282,9 +282,9 @@
try {
if (path === null) {
path = await window.NexusTerminal.wailsService.SFTPPwd(sessionId);
path = await window.AlchementaryLink.wailsService.SFTPPwd(sessionId);
}
var files = await window.NexusTerminal.wailsService.SFTPList(sessionId, path);
var files = await window.AlchementaryLink.wailsService.SFTPList(sessionId, path);
state.cwd = path;
state.files = this._sortFiles(files || []);
state.loading = false;
@@ -604,7 +604,7 @@
async _download(sessionId, filePath) {
try {
await window.NexusTerminal.wailsService.SFTPDownload(sessionId, filePath);
await window.AlchementaryLink.wailsService.SFTPDownload(sessionId, filePath);
this.app.notifications.success("Download started");
} catch (err) {
this.app.notifications.error(
@@ -617,7 +617,7 @@
var state = this.panels.get(sessionId);
if (!state) return;
try {
await window.NexusTerminal.wailsService.SFTPUpload(sessionId, state.cwd);
await window.AlchementaryLink.wailsService.SFTPUpload(sessionId, state.cwd);
this.app.notifications.success("Upload complete");
await this._refresh(sessionId);
} catch (err) {
@@ -634,7 +634,7 @@
if (!state) return;
var fullPath = pathJoin(state.cwd, name);
try {
await window.NexusTerminal.wailsService.SFTPMkdir(sessionId, fullPath);
await window.AlchementaryLink.wailsService.SFTPMkdir(sessionId, fullPath);
this.app.notifications.success("Directory created");
await this._refresh(sessionId);
} catch (err) {
@@ -647,7 +647,7 @@
if (!state) return;
var newPath = pathJoin(parentPath(oldPath), newName);
try {
await window.NexusTerminal.wailsService.SFTPRename(sessionId, oldPath, newPath);
await window.AlchementaryLink.wailsService.SFTPRename(sessionId, oldPath, newPath);
this.app.notifications.success("Renamed");
await this._refresh(sessionId);
} catch (err) {
@@ -657,7 +657,7 @@
async _delete(sessionId, entry) {
try {
await window.NexusTerminal.wailsService.SFTPRemove(sessionId, entry.path);
await window.AlchementaryLink.wailsService.SFTPRemove(sessionId, entry.path);
this.app.notifications.success(
(entry.isDir ? "Directory" : "File") + " deleted",
);
@@ -672,7 +672,7 @@
var failed = 0;
for (var i = 0; i < files.length; i++) {
try {
await window.NexusTerminal.wailsService.SFTPDownload(sessionId, files[i].path);
await window.AlchementaryLink.wailsService.SFTPDownload(sessionId, files[i].path);
} catch (err) {
failed++;
this.app.notifications.error(
@@ -706,7 +706,7 @@
var failed = 0;
for (var i = 0; i < entries.length; i++) {
try {
await window.NexusTerminal.wailsService.SFTPRemove(sessionId, entries[i].path);
await window.AlchementaryLink.wailsService.SFTPRemove(sessionId, entries[i].path);
} catch (err) {
failed++;
this.app.notifications.error(
@@ -1242,6 +1242,6 @@
}
/* ── Export ────────────────────────────────────────────────────────── */
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.SFTPBrowser = SFTPBrowser;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.SFTPBrowser = SFTPBrowser;
})();
+9 -9
View File
@@ -1,6 +1,6 @@
(function () {
/**
* Nexus Terminal — sidebar module.
* Alchementary Link — sidebar module.
* Renders the profile list with search, sections (favourites, recent, all),
* and context menus.
*/
@@ -12,7 +12,7 @@
protocolColour,
dismissContextMenus,
truncate,
} = window.NexusTerminal;
} = window.AlchementaryLink;
/** Maximum number of profiles shown in the "Recent" section. */
const RECENT_LIMIT = 5;
@@ -441,7 +441,7 @@
async _toggleFavourite(profile) {
try {
const newVal = !profile.favourite;
await window.NexusTerminal.wailsService.SetFavourite(profile.id, newVal);
await window.AlchementaryLink.wailsService.SetFavourite(profile.id, newVal);
profile.favourite = newVal;
this.app.state.updateProfile(profile);
this.render();
@@ -457,7 +457,7 @@
*/
async _duplicateProfile(profile) {
try {
const dup = await window.NexusTerminal.wailsService.DuplicateProfile(profile.id);
const dup = await window.AlchementaryLink.wailsService.DuplicateProfile(profile.id);
this.app.state.addProfile(dup);
this.render();
this.app.notifications.success(`Duplicated "${profile.displayName}"`);
@@ -478,7 +478,7 @@
if (!ok) return;
try {
await window.NexusTerminal.wailsService.DeleteProfile(profile.id);
await window.AlchementaryLink.wailsService.DeleteProfile(profile.id);
this.app.state.removeProfile(profile.id);
this.render();
this.app.notifications.success("Profile deleted");
@@ -687,7 +687,7 @@
}
try {
const saved = await window.NexusTerminal.wailsService.UpdateProfile(updated);
const saved = await window.AlchementaryLink.wailsService.UpdateProfile(updated);
this.app.state.updateProfile(saved);
} catch (err) {
this.app.notifications.error("Failed to update tags: " + err);
@@ -708,7 +708,7 @@
const updated = JSON.parse(JSON.stringify(p));
updated.folder = folder;
try {
const saved = await window.NexusTerminal.wailsService.UpdateProfile(updated);
const saved = await window.AlchementaryLink.wailsService.UpdateProfile(updated);
this.app.state.updateProfile(saved);
} catch (err) {
this.app.notifications.error("Failed to move group: " + err);
@@ -934,6 +934,6 @@
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.Sidebar = Sidebar;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.Sidebar = Sidebar;
})();
+10 -10
View File
@@ -1,11 +1,11 @@
(function () {
/**
* Nexus Terminal — snippet / command library.
* Alchementary Link — snippet / command library.
* Provides a panel for managing and sending reusable commands to active sessions.
*/
const { createElement, escapeHtml, dismissContextMenus } =
window.NexusTerminal;
window.AlchementaryLink;
class SnippetLibrary {
/**
@@ -91,8 +91,8 @@
/** @private */
_sendCommand(sessionId, command) {
try {
const b64 = window.NexusTerminal.utf8ToBase64(command + "\n");
window.NexusTerminal.wailsService.WriteSession(sessionId, b64);
const b64 = window.AlchementaryLink.utf8ToBase64(command + "\n");
window.AlchementaryLink.wailsService.WriteSession(sessionId, b64);
this.app.notifications.info("Snippet sent");
} catch (err) {
this.app.notifications.error("Failed to send snippet: " + err);
@@ -171,7 +171,7 @@
/** @private */
async _loadSnippets() {
try {
this.snippets = (await window.NexusTerminal.wailsService.ListSnippets()) || [];
this.snippets = (await window.AlchementaryLink.wailsService.ListSnippets()) || [];
} catch (err) {
this.snippets = [];
console.error("Failed to load snippets:", err);
@@ -334,7 +334,7 @@
});
deleteBtn.addEventListener("click", async () => {
try {
await window.NexusTerminal.wailsService.DeleteSnippet(snippet.id);
await window.AlchementaryLink.wailsService.DeleteSnippet(snippet.id);
this.snippets = this.snippets.filter((s) => s.id !== snippet.id);
this._renderList(modal, closeFn);
detailPanel.innerHTML =
@@ -418,10 +418,10 @@
let saved;
if (isNew) {
saved = await window.NexusTerminal.wailsService.CreateSnippet(data);
saved = await window.AlchementaryLink.wailsService.CreateSnippet(data);
this.snippets.push(saved);
} else {
saved = await window.NexusTerminal.wailsService.UpdateSnippet(data);
saved = await window.AlchementaryLink.wailsService.UpdateSnippet(data);
const idx = this.snippets.findIndex((s) => s.id === saved.id);
if (idx !== -1) this.snippets[idx] = saved;
}
@@ -449,6 +449,6 @@
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.SnippetLibrary = SnippetLibrary;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.SnippetLibrary = SnippetLibrary;
})();
+7 -7
View File
@@ -1,6 +1,6 @@
(function() {
/**
* Nexus Terminal — Split Pane Manager.
* Alchementary Link — Split Pane Manager.
* Enables splitting session panels into multiple panes, each hosting
* its own terminal session. Supports horizontal (top/bottom) and
* vertical (left/right) splits with draggable resize dividers.
@@ -522,10 +522,10 @@ class SplitPaneManager {
if (placeholder) placeholder.remove();
// Connect via backend
window.NexusTerminal.wailsService.ConnectSession(profile.id).then(async function(sessionInfo) {
window.AlchementaryLink.wailsService.ConnectSession(profile.id).then(async function(sessionInfo) {
if (!self._completePendingConnection(paneEl, ownerSessionId, token)) {
try {
await window.NexusTerminal.wailsService.DisconnectSession(sessionInfo.id);
await window.AlchementaryLink.wailsService.DisconnectSession(sessionInfo.id);
} catch (_) {
// The backend may already have completed the close.
}
@@ -556,10 +556,10 @@ class SplitPaneManager {
var placeholder = paneEl.querySelector('.split-pane-placeholder');
if (placeholder) placeholder.remove();
window.NexusTerminal.wailsService.ConnectLocal().then(async function(sessionInfo) {
window.AlchementaryLink.wailsService.ConnectLocal().then(async function(sessionInfo) {
if (!self._completePendingConnection(paneEl, ownerSessionId, token)) {
try {
await window.NexusTerminal.wailsService.DisconnectSession(sessionInfo.id);
await window.AlchementaryLink.wailsService.DisconnectSession(sessionInfo.id);
} catch (_) {
// The backend may already have completed the close.
}
@@ -791,7 +791,7 @@ class SplitPaneManager {
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.SplitPaneManager = SplitPaneManager;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.SplitPaneManager = SplitPaneManager;
})();
+9 -9
View File
@@ -1,10 +1,10 @@
(function () {
/**
* Nexus Terminal — SSH Key Manager.
* Alchementary Link — SSH Key Manager.
* Panel for listing, generating, copying, and deleting SSH key pairs.
*/
const { createElement, dismissContextMenus } = window.NexusTerminal;
const { createElement, dismissContextMenus } = window.AlchementaryLink;
class SSHKeyManager {
constructor(app) {
@@ -81,7 +81,7 @@
list.innerHTML = '<div class="sshkey-loading">Loading keys…</div>';
try {
const keys = await window.NexusTerminal.wailsService.ListSSHKeyPairs();
const keys = await window.AlchementaryLink.wailsService.ListSSHKeyPairs();
if (keys.length === 0) {
list.innerHTML =
'<div class="sshkey-empty">No SSH keys found in ~/.ssh/</div>';
@@ -138,7 +138,7 @@
async _copyPublicKey(key) {
try {
const pubKey = await window.NexusTerminal.wailsService.GetSSHPublicKey(key.name);
const pubKey = await window.AlchementaryLink.wailsService.GetSSHPublicKey(key.name);
if (window.runtime && window.runtime.ClipboardSetText) {
window.runtime.ClipboardSetText(pubKey);
} else {
@@ -163,7 +163,7 @@
async _deleteKey(key) {
try {
await window.NexusTerminal.wailsService.DeleteSSHKey(key.name);
await window.AlchementaryLink.wailsService.DeleteSSHKey(key.name);
this.app.notifications.success(`Key "${key.name}" deleted`);
this._loadKeys();
} catch (err) {
@@ -181,7 +181,7 @@
form.innerHTML = `
<div class="form-group">
<label class="form-label">Key Name</label>
<input type="text" class="form-input sshkey-name-input" placeholder="id_ed25519_nexus" autocomplete="off" spellcheck="false" />
<input type="text" class="form-input sshkey-name-input" placeholder="id_ed25519_link" autocomplete="off" spellcheck="false" />
</div>
<div class="form-group">
<label class="form-label">Type</label>
@@ -242,7 +242,7 @@
}
try {
await window.NexusTerminal.wailsService.GenerateSSHKey(
await window.AlchementaryLink.wailsService.GenerateSSHKey(
name,
keyType,
bits,
@@ -332,6 +332,6 @@
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.SSHKeyManager = SSHKeyManager;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.SSHKeyManager = SSHKeyManager;
})();
+3 -3
View File
@@ -1,6 +1,6 @@
(function () {
/**
* Nexus Terminal — application state management.
* Alchementary Link — application state management.
* Provides a centralised store for profiles, sessions, and preferences,
* emitting change events so the UI stays in sync.
*/
@@ -235,6 +235,6 @@
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.State = State;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.State = State;
})();
+5 -5
View File
@@ -1,6 +1,6 @@
(function () {
/**
* Nexus Terminal — tab management.
* Alchementary Link — tab management.
* Handles the tab bar: adding, removing, reordering, renaming,
* pinning tabs, and context menus.
*/
@@ -11,7 +11,7 @@
protocolColour,
stateLabel,
dismissContextMenus,
} = window.NexusTerminal;
} = window.AlchementaryLink;
class TabManager {
/**
@@ -192,7 +192,7 @@
* @private
*/
_confirmClose(sessionId) {
const createElement = window.NexusTerminal.createElement;
const createElement = window.AlchementaryLink.createElement;
const label = escapeHtml(this._tabLabel(sessionId));
return new Promise((resolve) => {
const backdrop = createElement("div", "modal-backdrop");
@@ -928,6 +928,6 @@
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.TabManager = TabManager;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.TabManager = TabManager;
})();
+28 -28
View File
@@ -1,6 +1,6 @@
(function () {
/**
* Nexus Terminal — xterm.js terminal manager.
* Alchementary Link — xterm.js terminal manager.
* Creates, manages, and destroys xterm.js instances for SSH and Telnet sessions.
* Handles data routing, resizing, and search.
*/
@@ -52,7 +52,7 @@
const themeName =
(this.app.state.preferences || {}).terminalTheme || "charcoal";
const term = new Terminal({
theme: window.NexusTerminal.getTerminalTheme(themeName),
theme: window.AlchementaryLink.getTerminalTheme(themeName),
fontFamily:
profile.terminalFontFamily ||
'JetBrains Mono, Menlo, Monaco, Consolas, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", monospace',
@@ -155,7 +155,7 @@
const data = seq.repeat(lines);
const b64 = utf8ToBase64(data);
try {
window.NexusTerminal.wailsService.WriteSession(sessionId, b64);
window.AlchementaryLink.wailsService.WriteSession(sessionId, b64);
} catch (_) {
/* non-critical */
}
@@ -263,7 +263,7 @@
try {
const b64 = utf8ToBase64(data);
window.NexusTerminal.wailsService.WriteSession(sessionId, b64);
window.AlchementaryLink.wailsService.WriteSession(sessionId, b64);
// Broadcast to other connected sessions if enabled
if (this.app.state.broadcastMode) {
@@ -275,7 +275,7 @@
const otherSession = this.app.state.getSession(otherId);
if (otherSession && otherSession.state === "connected") {
try {
window.NexusTerminal.wailsService.WriteSession(otherId, b64);
window.AlchementaryLink.wailsService.WriteSession(otherId, b64);
} catch (_) {
/* non-critical */
}
@@ -570,7 +570,7 @@
* @param {string} themeName
*/
applyTheme(themeName) {
const themeObj = window.NexusTerminal.getTerminalTheme(themeName);
const themeObj = window.AlchementaryLink.getTerminalTheme(themeName);
for (const [, entry] of this.terminals) {
entry.term.options.theme = themeObj;
}
@@ -584,7 +584,7 @@
if (cols <= 0 || rows <= 0) return;
Promise.resolve(
window.NexusTerminal.wailsService.ResizeSession(sessionId, cols, rows),
window.AlchementaryLink.wailsService.ResizeSession(sessionId, cols, rows),
).catch((err) => console.error("ResizeSession error:", err));
}
@@ -593,7 +593,7 @@
entry.localRestarting = true;
try {
await window.NexusTerminal.wailsService.RestartLocalSession(
await window.AlchementaryLink.wailsService.RestartLocalSession(
sessionId,
entry.term.cols,
entry.term.rows,
@@ -617,7 +617,7 @@
let overlay = panel.querySelector(".terminal-disconnect-overlay");
if (overlay) return overlay;
const { createElement } = window.NexusTerminal;
const { createElement } = window.AlchementaryLink;
if (!createElement) return null;
overlay = createElement("div", "terminal-disconnect-overlay");
@@ -653,7 +653,7 @@
* @private
*/
_showTerminalContextMenu(sessionId, term, x, y) {
const { createElement, dismissContextMenus } = window.NexusTerminal;
const { createElement, dismissContextMenus } = window.AlchementaryLink;
dismissContextMenus();
const isMac = navigator.platform.indexOf("Mac") !== -1;
@@ -813,7 +813,7 @@
}
_promptFileDropAction(files, canUpload) {
const { createElement, escapeHtml, formatBytes } = window.NexusTerminal;
const { createElement, escapeHtml, formatBytes } = window.AlchementaryLink;
return new Promise((resolve) => {
const backdrop = createElement("div", "modal-backdrop");
const modal = createElement("div", "modal modal-sm");
@@ -951,7 +951,7 @@
const totalBytes = typeof file.size === "number" ? file.size : 0;
let uploadId = "";
try {
uploadId = await window.NexusTerminal.wailsService.SFTPUploadStart(
uploadId = await window.AlchementaryLink.wailsService.SFTPUploadStart(
sessionId,
remoteDir,
file.name,
@@ -962,14 +962,14 @@
const end = Math.min(offset + UPLOAD_CHUNK_BYTES, totalBytes);
const buffer = await this._readFileSlice(file, offset, end);
const base64Data = this._arrayBufferToBase64(buffer);
await window.NexusTerminal.wailsService.SFTPUploadChunk(uploadId, base64Data);
await window.AlchementaryLink.wailsService.SFTPUploadChunk(uploadId, base64Data);
}
await window.NexusTerminal.wailsService.SFTPUploadFinish(uploadId);
await window.AlchementaryLink.wailsService.SFTPUploadFinish(uploadId);
} catch (err) {
if (uploadId) {
try {
await window.NexusTerminal.wailsService.SFTPUploadCancel(uploadId);
await window.AlchementaryLink.wailsService.SFTPUploadCancel(uploadId);
} catch (_) {
/* noop */
}
@@ -1001,7 +1001,7 @@
}
_openUploadDirectoryPicker(sessionId) {
const { createElement, escapeHtml } = window.NexusTerminal;
const { createElement, escapeHtml } = window.AlchementaryLink;
return new Promise((resolve) => {
const backdrop = createElement("div", "modal-backdrop");
const modal = createElement("div", "modal modal-lg");
@@ -1223,12 +1223,12 @@
renderLoading();
try {
const targetPath =
path || (await window.NexusTerminal.wailsService.SFTPPwd(sessionId));
const files = await window.NexusTerminal.wailsService.SFTPList(
path || (await window.AlchementaryLink.wailsService.SFTPPwd(sessionId));
const files = await window.AlchementaryLink.wailsService.SFTPList(
sessionId,
targetPath,
);
const stat = await window.NexusTerminal.wailsService.SFTPStat(
const stat = await window.AlchementaryLink.wailsService.SFTPStat(
sessionId,
targetPath,
);
@@ -1320,7 +1320,7 @@
}
_buildPickerRow(entry, index) {
const { createElement } = window.NexusTerminal;
const { createElement } = window.AlchementaryLink;
const dataset = { path: entry.path || "" };
if (typeof index === "number") {
dataset.index = String(index);
@@ -1395,7 +1395,7 @@
* @private
*/
_exportTerminalOutput(sessionId, term) {
const { createElement } = window.NexusTerminal;
const { createElement } = window.AlchementaryLink;
const buffer = term.buffer.active;
const lines = [];
for (let i = 0; i < buffer.length; i++) {
@@ -1438,7 +1438,7 @@
* @private
*/
_showPortForwardPanel(sessionId) {
const { createElement, escapeHtml } = window.NexusTerminal;
const { createElement, escapeHtml } = window.AlchementaryLink;
const session = this.app.state.getSession(sessionId);
if (!session || session.protocol !== "ssh") {
this.app.notifications.error(
@@ -1502,7 +1502,7 @@
const renderForwards = async () => {
try {
const forwards =
(await window.NexusTerminal.wailsService.ListPortForwards(sessionId)) || [];
(await window.AlchementaryLink.wailsService.ListPortForwards(sessionId)) || [];
listEl.innerHTML = "";
if (forwards.length === 0) {
listEl.innerHTML =
@@ -1528,7 +1528,7 @@
});
stopBtn.addEventListener("click", async () => {
try {
await window.NexusTerminal.wailsService.StopPortForward(sessionId, fwd.id);
await window.AlchementaryLink.wailsService.StopPortForward(sessionId, fwd.id);
self.app.notifications.info("Forward stopped");
renderForwards();
} catch (err) {
@@ -1573,7 +1573,7 @@
}
try {
await window.NexusTerminal.wailsService.StartPortForward(sessionId, {
await window.AlchementaryLink.wailsService.StartPortForward(sessionId, {
id: "",
direction: direction,
bindHost: bindHost,
@@ -1604,10 +1604,10 @@
_buildTheme() {
const themeName =
(this.app.state.preferences || {}).terminalTheme || "charcoal";
return window.NexusTerminal.getTerminalTheme(themeName);
return window.AlchementaryLink.getTerminalTheme(themeName);
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.TerminalManager = TerminalManager;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.TerminalManager = TerminalManager;
})();
+4 -4
View File
@@ -1,6 +1,6 @@
(function () {
/**
* Nexus Terminal — terminal colour themes.
* Alchementary Link — terminal colour themes.
* Provides a collection of preset xterm.js colour schemes and a manager
* for applying them to terminal instances.
*/
@@ -341,7 +341,7 @@
}));
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.getTerminalTheme = getTerminalTheme;
window.NexusTerminal.listTerminalThemes = listTerminalThemes;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.getTerminalTheme = getTerminalTheme;
window.AlchementaryLink.listTerminalThemes = listTerminalThemes;
})();
+5 -5
View File
@@ -1,13 +1,13 @@
(function () {
/**
* Nexus Terminal — Trigger Rules Engine.
* Alchementary Link — Trigger Rules Engine.
* Monitors terminal output and fires actions (highlight, notify, sound)
* when patterns match. Rules are stored in localStorage.
*/
const { createElement } = window.NexusTerminal;
const { createElement } = window.AlchementaryLink;
const STORAGE_KEY = "nexus:trigger-rules";
const STORAGE_KEY = "link:trigger-rules";
class TriggerEngine {
constructor(app) {
@@ -376,6 +376,6 @@
}
}
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.TriggerEngine = TriggerEngine;
window.AlchementaryLink = window.AlchementaryLink || {};
window.AlchementaryLink.TriggerEngine = TriggerEngine;
})();
+3 -3
View File
@@ -1,6 +1,6 @@
(function () {
/**
* Nexus Terminal — utility functions.
* Alchementary Link — utility functions.
* General-purpose helpers used throughout the application.
*/
@@ -348,8 +348,8 @@
}
}
window.NexusTerminal = window.NexusTerminal || {};
Object.assign(window.NexusTerminal, {
window.AlchementaryLink = window.AlchementaryLink || {};
Object.assign(window.AlchementaryLink, {
$,
$$,
createElement,
+3 -3
View File
@@ -1,11 +1,11 @@
(function () {
/**
* Nexus Terminal — VNC viewer.
* Alchementary Link — VNC viewer.
* Renders a noVNC canvas inside a session panel, connecting to the local
* WebSocket proxy started by the Go backend.
*/
const { createElement, $ } = window.NexusTerminal;
const { createElement, $ } = window.AlchementaryLink;
class VNCViewer {
constructor() {
@@ -327,5 +327,5 @@
}
// Export to global namespace.
window.NexusTerminal.VNCViewer = new VNCViewer();
window.AlchementaryLink.VNCViewer = new VNCViewer();
})();
+1 -1
View File
@@ -1,5 +1,5 @@
(function () {
const root = (window.NexusTerminal = window.NexusTerminal || {});
const root = (window.AlchementaryLink = window.AlchementaryLink || {});
const directService = () => window.go?.api?.Service || null;
const unavailable = (name) =>
+1 -1
View File
@@ -1,4 +1,4 @@
module github.com/andydixon/nexus
module github.com/andydixon/link
go 1.25.12
+18 -18
View File
@@ -24,17 +24,17 @@ import (
"github.com/pquerna/otp/totp"
wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime"
"github.com/andydixon/nexus/internal/audit"
"github.com/andydixon/nexus/internal/exportimport"
"github.com/andydixon/nexus/internal/localshell"
"github.com/andydixon/nexus/internal/models"
"github.com/andydixon/nexus/internal/preferences"
"github.com/andydixon/nexus/internal/secrets"
"github.com/andydixon/nexus/internal/sessions"
"github.com/andydixon/nexus/internal/sshconfig"
"github.com/andydixon/nexus/internal/storage"
"github.com/andydixon/nexus/internal/updater"
"github.com/andydixon/nexus/internal/validation"
"github.com/andydixon/link/internal/audit"
"github.com/andydixon/link/internal/exportimport"
"github.com/andydixon/link/internal/localshell"
"github.com/andydixon/link/internal/models"
"github.com/andydixon/link/internal/preferences"
"github.com/andydixon/link/internal/secrets"
"github.com/andydixon/link/internal/sessions"
"github.com/andydixon/link/internal/sshconfig"
"github.com/andydixon/link/internal/storage"
"github.com/andydixon/link/internal/updater"
"github.com/andydixon/link/internal/validation"
"github.com/google/uuid"
)
@@ -1291,9 +1291,9 @@ func (s *Service) ExportProfilesEncrypted(profileIDs []string, password string)
// Prompt for save location.
path, err := wailsRuntime.SaveFileDialog(s.ctx, wailsRuntime.SaveDialogOptions{
Title: "Export Encrypted Profiles",
DefaultFilename: "nexus-profiles.nexus",
DefaultFilename: "link-profiles.link",
Filters: []wailsRuntime.FileFilter{
{DisplayName: "Nexus Export (*.nexus)", Pattern: "*.nexus"},
{DisplayName: "Link Export (*.link)", Pattern: "*.link"},
},
})
if err != nil {
@@ -1303,9 +1303,9 @@ func (s *Service) ExportProfilesEncrypted(profileIDs []string, password string)
return "", fmt.Errorf("export cancelled")
}
// Ensure .nexus extension.
if filepath.Ext(path) != ".nexus" {
path += ".nexus"
// Ensure .link extension.
if filepath.Ext(path) != ".link" {
path += ".link"
}
if err := exportimport.EncryptAndExport(profiles, password, path); err != nil {
@@ -1327,7 +1327,7 @@ func (s *Service) ImportProfilesEncrypted(password string, strategy string) (int
path, err := wailsRuntime.OpenFileDialog(s.ctx, wailsRuntime.OpenDialogOptions{
Title: "Import Encrypted Profiles",
Filters: []wailsRuntime.FileFilter{
{DisplayName: "Nexus Export (*.nexus)", Pattern: "*.nexus"},
{DisplayName: "Link Export (*.link)", Pattern: "*.link"},
},
})
if err != nil {
@@ -2023,7 +2023,7 @@ func (s *Service) GenerateSSHKey(name string, keyType string, bits int, passphra
}
// Build ssh-keygen arguments
args := []string{"-t", keyType, "-f", keyPath, "-C", name + "@nexus-terminal"}
args := []string{"-t", keyType, "-f", keyPath, "-C", name + "@alchementary-link"}
if passphrase != "" {
args = append(args, "-N", passphrase)
} else {
+4 -4
View File
@@ -7,10 +7,10 @@ import (
"strings"
"testing"
"github.com/andydixon/nexus/internal/audit"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/nexus/internal/models"
"github.com/andydixon/nexus/internal/storage"
"github.com/andydixon/link/internal/audit"
"github.com/andydixon/link/internal/leviathan/document"
"github.com/andydixon/link/internal/models"
"github.com/andydixon/link/internal/storage"
)
type closeTrackingWriter struct {
+11 -11
View File
@@ -17,17 +17,17 @@ import (
wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime"
"github.com/andydixon/nexus/internal/leviathan/analysis"
"github.com/andydixon/nexus/internal/leviathan/binsnoop"
"github.com/andydixon/nexus/internal/leviathan/disasm"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/nexus/internal/leviathan/parsers"
"github.com/andydixon/nexus/internal/leviathan/patch"
"github.com/andydixon/nexus/internal/leviathan/pdf"
"github.com/andydixon/nexus/internal/leviathan/spectrogram"
"github.com/andydixon/nexus/internal/leviathan/squashfs"
"github.com/andydixon/nexus/internal/leviathan/stego"
"github.com/andydixon/nexus/internal/leviathan/transforms"
"github.com/andydixon/link/internal/leviathan/analysis"
"github.com/andydixon/link/internal/leviathan/binsnoop"
"github.com/andydixon/link/internal/leviathan/disasm"
"github.com/andydixon/link/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/parsers"
"github.com/andydixon/link/internal/leviathan/patch"
"github.com/andydixon/link/internal/leviathan/pdf"
"github.com/andydixon/link/internal/leviathan/spectrogram"
"github.com/andydixon/link/internal/leviathan/squashfs"
"github.com/andydixon/link/internal/leviathan/stego"
"github.com/andydixon/link/internal/leviathan/transforms"
)
type leviathanState struct {
+11 -11
View File
@@ -1,4 +1,4 @@
// Package app provides the core application lifecycle for Nexus Terminal.
// Package app provides the core application lifecycle for Alchementary Link.
// It wires together storage, secrets, sessions, preferences, known hosts,
// and the audit log, and exposes Wails lifecycle hooks.
package app
@@ -11,15 +11,15 @@ import (
"github.com/wailsapp/wails/v2/pkg/runtime"
"github.com/andydixon/nexus/internal/api"
"github.com/andydixon/nexus/internal/audit"
"github.com/andydixon/nexus/internal/config"
"github.com/andydixon/nexus/internal/knownhosts"
"github.com/andydixon/nexus/internal/preferences"
"github.com/andydixon/nexus/internal/recording"
"github.com/andydixon/nexus/internal/secrets"
"github.com/andydixon/nexus/internal/sessions"
"github.com/andydixon/nexus/internal/storage"
"github.com/andydixon/link/internal/api"
"github.com/andydixon/link/internal/audit"
"github.com/andydixon/link/internal/config"
"github.com/andydixon/link/internal/knownhosts"
"github.com/andydixon/link/internal/preferences"
"github.com/andydixon/link/internal/recording"
"github.com/andydixon/link/internal/secrets"
"github.com/andydixon/link/internal/sessions"
"github.com/andydixon/link/internal/storage"
)
// App is the central application struct. Wails binds its lifecycle methods
@@ -85,7 +85,7 @@ func (a *App) Startup(ctx context.Context) {
// Initialise secret stores (keychain preferred, encrypted file fallback).
a.SecretStore = secrets.NewSecretStore(cfg.SecretPath)
a.KeychainStore = secrets.NewKeychainStore("NexusTerminal")
a.KeychainStore = secrets.NewKeychainStore("AlchementaryLink")
// Initialise the audit logger backed by SQLite.
a.AuditLog = audit.NewLogger(store)
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"fmt"
"time"
"github.com/andydixon/nexus/internal/models"
"github.com/andydixon/link/internal/models"
"github.com/google/uuid"
)
+6 -6
View File
@@ -7,7 +7,7 @@ import (
"runtime"
)
const appName = "NexusTerminal"
const appName = "AlchementaryLink"
// AppConfig holds the resolved application configuration paths.
type AppConfig struct {
@@ -19,9 +19,9 @@ type AppConfig struct {
// Load resolves the application configuration.
// Data directory follows platform conventions:
// - macOS: ~/Library/Application Support/NexusTerminal
// - Linux: ~/.config/NexusTerminal
// - Windows: %APPDATA%/NexusTerminal
// - macOS: ~/Library/Application Support/AlchementaryLink
// - Linux: ~/.config/AlchementaryLink
// - Windows: %APPDATA%/AlchementaryLink
func Load() (*AppConfig, error) {
dataDir, err := resolveDataDir()
if err != nil {
@@ -30,9 +30,9 @@ func Load() (*AppConfig, error) {
cfg := &AppConfig{
DataDir: dataDir,
DBPath: filepath.Join(dataDir, "nexus.db"),
DBPath: filepath.Join(dataDir, "link.db"),
SecretPath: filepath.Join(dataDir, "vault"),
LogPath: filepath.Join(dataDir, "nexus.log"),
LogPath: filepath.Join(dataDir, "link.log"),
}
return cfg, nil
}
+1 -1
View File
@@ -18,7 +18,7 @@ import (
"golang.org/x/crypto/argon2"
"github.com/andydixon/nexus/internal/models"
"github.com/andydixon/link/internal/models"
)
const (
+8 -8
View File
@@ -6,12 +6,12 @@ import (
"runtime"
"testing"
"github.com/andydixon/nexus/internal/models"
"github.com/andydixon/link/internal/models"
)
func TestEncryptDecryptRoundTrip(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test-export.nexus")
path := filepath.Join(dir, "test-export.link")
profiles := []models.Profile{
{
@@ -73,7 +73,7 @@ func TestEncryptDecryptRoundTrip(t *testing.T) {
func TestDecryptWrongPassword(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test-export.nexus")
path := filepath.Join(dir, "test-export.link")
profiles := []models.Profile{
{
@@ -97,7 +97,7 @@ func TestDecryptWrongPassword(t *testing.T) {
func TestEncryptEmptyProfiles(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.nexus")
path := filepath.Join(dir, "test.link")
err := EncryptAndExport(nil, "password", path)
if err == nil {
@@ -109,7 +109,7 @@ func TestEncryptAndExportRestrictsExistingFilePermissions(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("POSIX permission bits are not enforced on Windows")
}
path := filepath.Join(t.TempDir(), "profiles.nexus")
path := filepath.Join(t.TempDir(), "profiles.link")
if err := os.WriteFile(path, []byte("old"), 0644); err != nil {
t.Fatal(err)
}
@@ -127,7 +127,7 @@ func TestEncryptAndExportRestrictsExistingFilePermissions(t *testing.T) {
func TestEncryptEmptyPassword(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "test.nexus")
path := filepath.Join(dir, "test.link")
profiles := []models.Profile{{ID: "1"}}
err := EncryptAndExport(profiles, "", path)
@@ -138,7 +138,7 @@ func TestEncryptEmptyPassword(t *testing.T) {
func TestDecryptCorruptFile(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "corrupt.nexus")
path := filepath.Join(dir, "corrupt.link")
// Write garbage data.
if err := os.WriteFile(path, []byte("corrupt"), 0600); err != nil {
@@ -153,7 +153,7 @@ func TestDecryptCorruptFile(t *testing.T) {
func TestDecryptRejectsOversizedFile(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "huge.nexus")
path := filepath.Join(dir, "huge.link")
f, err := os.Create(path)
if err != nil {
t.Fatal(err)
+5 -5
View File
@@ -17,7 +17,7 @@ import (
"sync/atomic"
"time"
"github.com/andydixon/nexus/internal/models"
"github.com/andydixon/link/internal/models"
)
const (
@@ -90,7 +90,7 @@ func (c *Client) fetchRepos(ctx context.Context, progress func(done int, total i
}
req.Header.Set("Authorization", "Bearer "+c.token)
req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("User-Agent", "NexusTerminal/1.0")
req.Header.Set("User-Agent", "AlchementaryLink/1.0")
resp, err := c.httpClient.Do(req)
if err != nil {
@@ -210,7 +210,7 @@ func (c *Client) FetchLatestCommit(ctx context.Context, fullName string, ref str
}
req.Header.Set("Authorization", "Bearer "+c.token)
req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("User-Agent", "NexusTerminal/1.0")
req.Header.Set("User-Agent", "AlchementaryLink/1.0")
resp, err := c.httpClient.Do(req)
if err != nil {
@@ -280,7 +280,7 @@ func (c *Client) DownloadRepoArchive(ctx context.Context, fullName string, ref s
}
req.Header.Set("Authorization", "Bearer "+c.token)
req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("User-Agent", "NexusTerminal/1.0")
req.Header.Set("User-Agent", "AlchementaryLink/1.0")
resp, err := c.httpClient.Do(req)
if err != nil {
@@ -319,7 +319,7 @@ func (c *Client) ValidateToken(ctx context.Context) (string, error) {
}
req.Header.Set("Authorization", "Bearer "+c.token)
req.Header.Set("Accept", "application/vnd.github+json")
req.Header.Set("User-Agent", "NexusTerminal/1.0")
req.Header.Set("User-Agent", "AlchementaryLink/1.0")
resp, err := c.httpClient.Do(req)
if err != nil {
+3 -3
View File
@@ -1,4 +1,4 @@
// Package ircclient wraps a full IRC client with Nexus-specific event and
// Package ircclient wraps a full IRC client with Link-specific event and
// command handling, including optional SSH tunnelling.
package ircclient
@@ -216,7 +216,7 @@ func (c *Client) Close() {
c.mu.Unlock()
if c.client != nil {
c.client.Quit("Nexus Terminal disconnected")
c.client.Quit("Alchementary Link disconnected")
c.client.Close()
}
if c.sshClient != nil {
@@ -305,7 +305,7 @@ func (c *Client) HandleInput(target string, input string) error {
}
case "quit":
if args == "" {
args = "Nexus Terminal disconnected"
args = "Alchementary Link disconnected"
}
c.client.Quit(args)
c.client.Close()
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"fmt"
"time"
"github.com/andydixon/nexus/internal/models"
"github.com/andydixon/link/internal/models"
"github.com/google/uuid"
)
+1 -1
View File
@@ -3,7 +3,7 @@ package knownhosts
import (
"testing"
"github.com/andydixon/nexus/internal/models"
"github.com/andydixon/link/internal/models"
)
// ---------------------------------------------------------------------------
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"encoding/binary"
"io"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// Signature describes a carveable object. `validate` is run against
+1 -1
View File
@@ -3,7 +3,7 @@ package analysis
import (
"context"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// DiffRegion describes a contiguous run of differing bytes.
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"context"
"math"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// Entropy computes Shannon entropy per sliding window over the range.
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"strconv"
"strings"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// fuzzy.go implements context-triggered piecewise hashing (CTPH) compatible
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"hash"
"hash/crc32"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// HashSet contains CRC32/MD5/SHA-1/SHA-256/SHA-512 for a range.
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"context"
"regexp"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// ioc regexes. They run against printable ASCII strings extracted
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"fmt"
"strings"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// KeyArtefact is a single key-ish object we've found in the raw file.
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"regexp"
"strings"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
type MultiSearchQuery struct {
+1 -1
View File
@@ -1,7 +1,7 @@
package analysis
import (
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// Overview returns a compact first-pass report intended for both UI
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"strconv"
"strings"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// Rule represents a lightweight YARA-like rule. A rule matches when
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"errors"
"regexp"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// SearchHit describes one search result.
+1 -1
View File
@@ -3,7 +3,7 @@ package analysis
import (
"context"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// StringHit represents one extracted string.
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"math"
"time"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// ScanTimestamps walks the range looking for plausible timestamps in
+2 -2
View File
@@ -1,8 +1,8 @@
package disasm
import (
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/nexus/internal/leviathan/parsers"
"github.com/andydixon/link/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/parsers"
)
// ImageAnalysis is a metadata-seeded analysis across file-backed executable
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"fmt"
"strings"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// ParseASN1 produces a field tree for the DER TLV structure starting
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"fmt"
"strings"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// ParseCerts scans the whole document for PEM-encoded certificates
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"fmt"
"sort"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// ExecutableRegion is a file-backed executable code range.
+1 -1
View File
@@ -12,7 +12,7 @@ import (
"unicode/utf16"
"unicode/utf8"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// Field is one node of the parsed structure.
+1 -1
View File
@@ -15,7 +15,7 @@ import (
"strings"
"time"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// leaf builds a terminal Field with no children.
+1 -1
View File
@@ -5,7 +5,7 @@ import (
"errors"
"fmt"
"github.com/andydixon/nexus/internal/leviathan/document"
"github.com/andydixon/link/internal/leviathan/document"
)
// Mach-O magic values (32/64, LE/BE) plus fat (universal) magic.

Some files were not shown because too many files have changed in this diff Show More