Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
4635fd1
Initial Endermux protocol, client, server, and Fabric implementation
jpenilla Jan 24, 2026
ec88fbe
Forward log event throwables
jpenilla Feb 5, 2026
a9747aa
Exit on proto mismatch, backoff on reconnect failures with existing s…
jpenilla Feb 5, 2026
42c2dfc
Add exit messages
jpenilla Feb 5, 2026
d41d176
Move RemoteLogForwarder to server
jpenilla Feb 5, 2026
6c9ef2d
Refactor implementation hooks
jpenilla Feb 5, 2026
8945ac6
Add interactivity status (allows initializing earlier and properly av…
jpenilla Feb 6, 2026
5944f66
Move around log4j plugins to avoid platform conflicts
jpenilla Feb 6, 2026
09ff801
Improve client logging setup
jpenilla Feb 6, 2026
561d768
Fix error when spamming enter after stopping server
jpenilla Feb 6, 2026
1c1f171
Improve interrupt handling and shutdown process robustness
jpenilla Feb 6, 2026
bc477cf
Split EndermuxClient for easier maintenance, add dumb terminal fallba…
jpenilla Feb 6, 2026
738fd7d
Improve dumb terminal detection
jpenilla Feb 6, 2026
22b2fe8
Don't publish socket until ready to accept connections
jpenilla Feb 8, 2026
f969a00
Use adventure-text-logger-slf4j in client
jpenilla Feb 8, 2026
80b3a7a
Remove unused connection close handling
jpenilla Feb 8, 2026
5e16a32
More disconnection logic cleanup
jpenilla Feb 8, 2026
33a0a7c
server logging cleanup
jpenilla Feb 8, 2026
4b8cc76
send reject reason immediately to avoid generic disconnect on rejection
jpenilla Feb 8, 2026
71fa164
make command execution fire-and-forget and harden transport errors
jpenilla Feb 8, 2026
bb02779
bump protocol to v8 and send commands as unsolicited
jpenilla Feb 8, 2026
2fc22dc
remove dead handshake-timeout reject branch
jpenilla Feb 8, 2026
99f9008
Initial spec and agents md
jpenilla Feb 8, 2026
b9da575
Update Gradle wrapper
jpenilla Feb 8, 2026
f6f4cb2
Setup unit test wiring
jpenilla Feb 8, 2026
92c3043
endermux-common unit tests
jpenilla Feb 8, 2026
064e047
endermux-server integration test
jpenilla Feb 8, 2026
6510a23
Add SocketTransportIntegrationTest
jpenilla Feb 8, 2026
fefdc61
endermux: preserve full throwable proxy context across log forwarding
jpenilla Feb 8, 2026
81932c1
endermux: raise max frame size to 1 MiB and bump protocol to v10
jpenilla Feb 8, 2026
7464043
Enable config cache
jpenilla Feb 8, 2026
7af73e6
reorder imports
jpenilla Feb 8, 2026
3461c53
feat(endermux-client): async highlight responsiveness and queued tran…
jpenilla Feb 10, 2026
27eb13d
test(endermux-server): harden integration test socket reads against b…
jpenilla Feb 10, 2026
05a1556
refactor: move console logging to Log4j config and rework Endermux st…
jpenilla Feb 13, 2026
316b04e
Not compatible with better_log4j_config mod
jpenilla Feb 13, 2026
0f73149
Add comment
jpenilla Feb 13, 2026
3f11916
Improve README
jpenilla Feb 14, 2026
0b10522
Split endermux into a separate repo
jpenilla Feb 14, 2026
ef02a55
Update actions
jpenilla Feb 14, 2026
3096dfe
Update README/AGENTS
jpenilla Feb 14, 2026
cdc04ce
Use HTTPS for submodule
jpenilla Feb 14, 2026
33ae90e
Clean up version catalog
jpenilla Feb 14, 2026
db6e6b6
Update loom settings configuration
jpenilla Feb 14, 2026
afbc1fd
Bump version
jpenilla Feb 14, 2026
7692151
Update compatibility metadata
jpenilla Feb 14, 2026
db02b9f
Remove redundant gson dependency
jpenilla Feb 14, 2026
8fd90d3
Bump Gradle runtime JVM in CI
jpenilla Feb 14, 2026
49947ab
Print full exceptions in default log4j config
jpenilla Feb 14, 2026
8765aeb
Revert exception print changes
jpenilla Feb 14, 2026
3fa848b
feat: wire system-message rich context into endermux server-side ren…
jpenilla Feb 15, 2026
d0ad765
Cleanup FabricEndermux
jpenilla Feb 15, 2026
325094b
Send completion tooltips with proper color level
jpenilla Feb 15, 2026
0d050eb
Update AGENTS.md
jpenilla Feb 17, 2026
0039fb2
Update Gradle wrapper
jpenilla Apr 16, 2026
0438ca8
Build with 26.1.2
jpenilla Apr 16, 2026
f3d0a07
Add Endermux experimental warning
jpenilla Apr 16, 2026
057c547
Update loom
jpenilla Apr 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ jobs:
modrinth-publish: true
artifacts-path: 'build/libs/better-fabric-console-mc*.jar'
loom: true
jdk-version: 21
jdk-version: 25
init-submodules: true
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ classes/
# eclipse

*.launch
.factorypath

# idea

Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "endermux"]
path = endermux
url = https://github.com/jpenilla/endermux.git
17 changes: 17 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Fast Start

- See `README.md` for background info.
- To test compilation, run: `./gradlew compileJava`.
- To run checks, use: `./gradlew check`.
- Verify your work at the end even if not explicitly requested.

## Endermux

- Endermux modules live in the `endermux/` git submodule and are wired into this repo via `includeBuild("./endermux")` in `settings.gradle.kts`.
- For any work in Endermux code/docs, read and follow `endermux/AGENTS.md` first.
- When validating Endermux changes from the repo root, either `cd endermux` and run its tasks directly, or use composite-prefixed tasks like `./gradlew :endermux:endermux-common:compileJava :endermux:endermux-server:compileJava :endermux:endermux-client:compileJava`.

## Documentation

- Keep `AGENTS.md` in sync with repository structure and build setup.
- If behavior and docs diverge, fix both in the same change.
86 changes: 84 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,87 @@
# Better Fabric Console

Server-side Fabric mod enhancing the console with tab completions, colored log output, command syntax highlighting, command history, and more.
<img src="src/main/resources/assets/better-fabric-console/icon.png" alt="Better Fabric Console icon" width="128" />

Better Fabric Console is configurable through the `better-fabric-console.conf` file generated in the config folder. Requires [Fabric-API](https://modrinth.com/mod/fabric-api).
Better Fabric Console is a server-side Fabric mod that replaces the vanilla dedicated server console experience with an interactive, color-aware terminal.

## Features

- Brigadier-aware command completion in the server console
- Command syntax highlighting with configurable argument colors
- Colored log output in terminal consoles
- Persistent command history in `.console_history`
- Optional logging of player-executed commands
- Optional [Endermux](https://github.com/jpenilla/endermux) remote console server

![Screenshot 1](docs/images/screenshot_1.png)

## Requirements

- Fabric server
- [Fabric API](https://modrinth.com/mod/fabric-api)
- [adventure-platform-fabric](https://modrinth.com/mod/adventure-platform-mod)

## Installation

1. Download the latest Better Fabric Console, Fabric API, and adventure-platform-fabric jars.
2. Place the jars in your server `mods/` directory.
3. Start the server once to generate config files.
4. Stop the server and edit configuration as needed.
5. Start the server again.

You may also choose to run the server with the `--nogui` argument (i.e. `java -jar fabric.jar --nogui`)
to disable the server GUI (this is a Vanilla option).

## Configuration

Better Fabric Console generates and reads:

- `config/better-fabric-console/better-fabric-console.conf`
- `config/better-fabric-console/log4j2.xml`

Default `better-fabric-console.conf` for reference:

```hocon
# Specify argument highlight colors, in order. Possible values: [BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE]
highlight-colors=[
CYAN,
YELLOW,
GREEN,
MAGENTA,
BLUE
]
# Whether to log commands executed by players to console.
log-player-executed-commands=true
# Endermux remote console server configuration.
endermux {
# Whether to enable the Endermux remote console server.
enabled=false
# Path to the socket file relative to the server directory.
socket-path="console.sock"
# Maximum number of concurrent socket connections.
max-connections=5
}
```

[Click here](src/main/resources/better-fabric-console-default-log4j2.xml) to see the default `log4j2.xml` configuration.

Better Fabric Console also respects the following system properties to override terminal environment detection:
- Adventure ANSI properties: https://docs.papermc.io/adventure/serializer/ansi/

Changes require a server restart.

## Endermux Remote Console Server

Better Fabric Console also includes the [Endermux](https://github.com/jpenilla/endermux) remote console server (disabled by default, see configuration above).

This allows for attaching to a fully-featured console session from a remote (over Unix domain socket) terminal using the Endermux client.

An example use case for this would be running the server as a systemd service and using Endermux when an interactive console is needed.

## Command

- `/better-fabric-console` (operator-level): Prints mod version information.

## License

MIT. See `license.txt`.
65 changes: 41 additions & 24 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,52 @@ import me.modmuss50.mpp.ReleaseType
import xyz.jpenilla.resourcefactory.fabric.Environment

plugins {
val indraVersion = "4.0.0"
id("net.kyori.indra") version indraVersion
id("net.kyori.indra.checkstyle") version indraVersion
id("net.kyori.indra.licenser.spotless") version indraVersion
alias(libs.plugins.indra)
alias(libs.plugins.indraCheckstyle)
alias(libs.plugins.indraLicenserSpotless)
id("xyz.jpenilla.quiet-fabric-loom")
id("me.modmuss50.mod-publish-plugin") version "1.1.0"
id("xyz.jpenilla.resource-factory-fabric-convention") version "1.3.1"
alias(libs.plugins.modPublishPlugin)
alias(libs.plugins.resourceFactoryFabricConvention)
}

version = "1.3.0-SNAPSHOT"
version = "2.0.0-SNAPSHOT"
group = "xyz.jpenilla"
description = "Server-side Fabric mod enhancing the console with tab completions, colored log output, command syntax highlighting, command history, and more."

val minecraftVersion = "26.1-snapshot-4"
val minecraftVersion = libs.versions.minecraft.get()

dependencies {
minecraft("com.mojang:minecraft:$minecraftVersion")
implementation("net.fabricmc:fabric-loader:0.18.4")
implementation("net.fabricmc.fabric-api:fabric-api:0.142.1+26.1")
minecraft(libs.minecraft)
implementation(libs.fabricLoader)
implementation(libs.fabricApi)

annotationProcessor("org.apache.logging.log4j:log4j-core:2.25.3")
implementation("xyz.jpenilla:endermux-common")
include("xyz.jpenilla:endermux-common")
implementation("xyz.jpenilla:endermux-server")
include("xyz.jpenilla:endermux-server")

val jlineVersion = "3.30.6"
implementation("org.jline:jline:$jlineVersion")
include("org.jline:jline:$jlineVersion")
implementation("org.jline:jline-terminal-jansi:$jlineVersion")
include("org.jline:jline-terminal-jansi:$jlineVersion")
implementation(libs.bundles.jline)
include(libs.bundles.jline)

implementation("org.fusesource.jansi:jansi:2.4.2")
include("org.fusesource.jansi:jansi:2.4.2")
// Fallback in case native access flags not passed - TODO verify this works as intended
implementation(libs.jansi)
include(libs.jansi)
implementation(libs.jline.terminal.jansi)
include(libs.jline.terminal.jansi)

implementation("net.kyori:adventure-platform-fabric:6.9.0")
annotationProcessor(platform(libs.log4jBom))
annotationProcessor(libs.log4jCore)

implementation(transitiveInclude("org.spongepowered:configurate-hocon:4.2.0") {
implementation(libs.adventurePlatformFabric)

transitiveInclude(libs.configurateHocon) {
exclude("net.kyori", "option") // provided by adventure-platform-fabric
}
implementation(libs.configurateHocon) {
exclude("net.kyori", "option") // provided by adventure-platform-fabric
})
}

compileOnly("org.jspecify:jspecify:1.0.0")
compileOnly(libs.jspecify)
}

indra {
Expand All @@ -63,15 +71,22 @@ fabricModJson {
mixin("better-fabric-console.mixins.json")
depends("fabricloader", ">=0.18.4")
depends("fabric-api", "*")
depends("minecraft", ">1.21.11", "<26.2") // TODO ">=$minecraftVersion")
depends("minecraft", ">=$minecraftVersion", "<26.2")
depends("adventure-platform-fabric", "*")
breaks("better_log4j_config", "*")
breaks("jline4mcdsrv", "*")
}

tasks {
jar {
from("LICENSE")
archiveFileName.set("${project.name}-mc$minecraftVersion-${project.version}.jar")
}
runServer {
// jvmArgs("-Dmixin.debug=true")

// systemProperty("better-fabric-console.log.level", "debug")
}
}

publishMods.modrinth {
Expand All @@ -84,4 +99,6 @@ publishMods.modrinth {
modLoaders.add("fabric")
requires("fabric-api")
requires("adventure-platform-mod")
incompatible("better-log4j-config")
incompatible("jline4mcdsrv")
}
Binary file added docs/images/screenshot_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions endermux
Submodule endermux added at 78af08
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
org.gradle.jvmargs=-Xmx2G
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.configuration-cache=true
45 changes: 45 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[versions]
indra = "4.0.0"
mod-publish-plugin = "1.1.0"
resource-factory-fabric-convention = "1.3.1"

minecraft = "26.1.2"
fabric-loader = "0.19.2"
fabric-api = "0.146.0+26.1.2"

log4j = "2.25.3"
jline = "3.30.6"
jansi = "2.4.2"
adventure-platform-fabric = "6.9.0"
configurate-hocon = "4.2.0"
jspecify = "1.0.0"

[plugins]
indra = { id = "net.kyori.indra", version.ref = "indra" }
indraCheckstyle = { id = "net.kyori.indra.checkstyle", version.ref = "indra" }
indraLicenserSpotless = { id = "net.kyori.indra.licenser.spotless", version.ref = "indra" }
modPublishPlugin = { id = "me.modmuss50.mod-publish-plugin", version.ref = "mod-publish-plugin" }
resourceFactoryFabricConvention = { id = "xyz.jpenilla.resource-factory-fabric-convention", version.ref = "resource-factory-fabric-convention" }

[libraries]
minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" }
fabricLoader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
fabricApi = { module = "net.fabricmc.fabric-api:fabric-api", version.ref = "fabric-api" }

log4jBom = { module = "org.apache.logging.log4j:log4j-bom", version.ref = "log4j" }
log4jCore = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" }
log4jApi = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" }
log4jIostreams = { module = "org.apache.logging.log4j:log4j-iostreams", version.ref = "log4j" }

jline = { module = "org.jline:jline", version.ref = "jline" }
jline-terminal-ffm = { module = "org.jline:jline-terminal-ffm", version.ref = "jline" }
jline-terminal-jansi = { module = "org.jline:jline-terminal-jansi", version.ref = "jline" }
jansi = { module = "org.fusesource.jansi:jansi", version.ref = "jansi" }

adventurePlatformFabric = { module = "net.kyori:adventure-platform-fabric", version.ref = "adventure-platform-fabric" }

configurateHocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate-hocon" }
jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" }

[bundles]
jline = ["jline", "jline-terminal-ffm"]
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
2 changes: 1 addition & 1 deletion gradlew

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ pluginManagement {
}

plugins {
id("xyz.jpenilla.quiet-fabric-loom") version "1.15-SNAPSHOT" apply false
id("xyz.jpenilla.quiet-fabric-loom-repositories") version "1.16-SNAPSHOT"
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}

plugins.apply(net.fabricmc.loom.LoomRepositoryPlugin::class.java)

rootProject.name = "better-fabric-console"

includeBuild("./endermux")
Loading