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
+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) =>