Java-based Enterprise AI Harness — AI-powered automation platform built in pure Java on Abundent's fork of the Play Framework 1.x with a Nuxt frontend. Combines agent orchestration and job scheduling with no Spring, no Node.js, and no Python runtime.
19
stars
3,959
commits
Java
primary language
Sep 10, 2026
updated
JAVA FIRST. NO BLOAT. PURE POWER.
Licensing at a glance. JClaw is source-available, dual-licensed by Abundent Sdn Bhd: free for noncommercial use under the PolyForm Noncommercial License 1.0.0; any commercial use requires a commercial license. JClaw is not open-source software. Versions through v0.15.4 remain available under their original MIT License.
Get JClaw running in one command. The installer downloads the self-contained
jclaw-bundle.zip from the latest GitHub Release, verifies Java 25+ (the bundle's
only runtime dependency), extracts it to ~/.jclaw, and starts JClaw on
http://localhost:9000.
macOS & Linux
curl -fsSL https://raw.githubusercontent.com/tsukhani/jclaw/main/install.sh | sh
Windows (PowerShell)
irm https://raw.githubusercontent.com/tsukhani/jclaw/main/install.ps1 | iex
On Windows the bundle runs through Git Bash or WSL (the launcher is a POSIX shell script). The installer prefers Git Bash; if only WSL is present it launches there; if neither is found it installs and prints how to run it.
Once running, manage it with jclaw status, jclaw stop, jclaw restart from
any new shell — the installer puts the jclaw command on your PATH (via
~/.local/bin) and wires up <TAB> completion for bash and zsh. To remove JClaw
entirely, run jclaw uninstall: it stops the app, undoes the PATH and completion
wiring, and deletes ~/.jclaw.
Updating: jclaw upgrade (or Settings → Upgrade in the app) installs the
newest release in place. Your database, workspace, credentials, installed apps and
edited configuration are carried across; the database is backed up first; and a
release that fails to start is rolled back automatically. The download runs while
JClaw keeps serving, so only the swap itself is downtime. jclaw upgrade --check
reports what's available without installing it. Re-running the one-line installer
does the same thing — it hands off to jclaw upgrade when an install already
exists. Docker deployments upgrade the image instead
(docker compose pull && docker compose up -d), and a git clone uses git pull.
Requirements: a Java 25+ runtime (Zulu or Temurin). Nothing else — the bundle bakes in the framework, app dependencies, precompiled classes, and the prebuilt SPA.
Configuration (optional environment variables):
| Variable | Default | Purpose |
|---|---|---|
JCLAW_HOME | ~/.jclaw | Install directory |
JCLAW_VERSION | latest | Pin a release tag, e.g. v0.14.7 |
JCLAW_PORT | 9000 | Port reported on launch |
JCLAW_NO_START | — | Set to 1 to install without starting |
JCLAW_BUNDLE_URL | — | Install from a specific bundle URL (including file://) instead of GitHub Releases |
# Pin a version and install without auto-starting:
curl -fsSL https://raw.githubusercontent.com/tsukhani/jclaw/main/install.sh \
| JCLAW_VERSION=v0.14.7 JCLAW_NO_START=1 sh
JClaw is Abundent's AI-powered automation platform, built from scratch in pure Java on a customized Play Framework 1.x foundation. It draws ideas and feature designs from three predecessor projects:
The implementation is entirely original — no code is shared with either project. JClaw is built on lean library primitives (OkHttp 5, db-scheduler, ProcessBuilder, virtual threads, JPA) with no Spring, no heavy framework bloat, no Python, and no Node.js runtime on the server. The result is a leaner, faster, more maintainable platform for building AI agents and automation workflows.
Web chat with memory-aware agents, tool execution, and markdown rendering.
jclaw/
├── app/ # Application code
│ ├── controllers/ # HTTP controllers (Play 1.x pattern)
│ ├── models/ # JPA domain entities
│ ├── services/ # Business logic (incl. db-scheduler bridge)
│ ├── agents/ # AI agent implementations
│ ├── channels/ # Messaging channels (web, Telegram, Slack)
│ ├── llm/ # LLM provider drivers (OkHttp 5)
│ ├── tools/ # Agent tool implementations
│ ├── memory/ # Agent memory stores (JPA-backed)
│ ├── mcp/ # Model Context Protocol client
│ ├── slash/ # Slash-command handlers
│ ├── jobs/ # Play @Every jobs + db-scheduler handlers
│ ├── views/ # Groovy server templates
│ └── utils/ # Utility classes
├── conf/ # Play configuration
│ ├── application.conf # Main app config
│ ├── routes # URL routing
│ ├── play.plugins # Play plugin registration
│ └── log4j2.xml # Logging configuration
├── frontend/ # Nuxt 4 SPA (SPA-only; ssr: false)
│ ├── app.vue # Root component
│ ├── layouts/ # Page layouts
│ ├── pages/ # Nuxt file-based routes
│ ├── components/ # Reusable Vue components
│ ├── composables/ # Shared reactive state (useAuth, useEventBus, ...)
│ ├── middleware/ # Global route middleware (auth guard)
│ ├── public/ # Static assets
│ └── nuxt.config.ts # Nuxt configuration
├── evals/ # Agent-behaviour eval datasets
├── lib/ # Custom JARs (if needed)
├── modules/ # Play modules (auto-managed)
├── public/ # Static web assets
├── test/ # Unit and integration tests
├── tmp/ # Play temp/runtime files
├── logs/ # Application logs
└── README.md # This file
That's the whole list. The Abundent Play 1.x fork,
app dependencies, precompiled classes, and the prebuilt SPA all ship inside
jclaw-bundle.zip, so a Java 25 runtime is the only thing the host needs to run
JClaw — see Quick Install. (Building from source instead
adds a dev toolchain — Node.js, pnpm, and the play CLI — which the
Dev Container installs for you.)
Tesseract OCR — required for text extraction from images, scanned PDFs,
and image-only PDFs via the documents tool. Apache Tika invokes the
tesseract binary as a subprocess; without it, image inputs return empty
text. A startup probe logs a WARN line at boot if tesseract is missing so
the missing capability is visible without trial and error.
# Debian / Ubuntu
sudo apt-get install tesseract-ocr
# macOS
brew install tesseract
# Windows
choco install tesseract
# or: winget install --id UB-Mannheim.TesseractOCR
Additional language packs install separately. The default is English
(eng); install tesseract-ocr-fra, tesseract-ocr-jpn, etc. for other
languages, then update ocr.tesseract.languages in conf/application.conf
(e.g. eng+fra+jpn).
Local Ollama — required only if you want to bind agents to the
ollama-local LLM provider for self-hosted inference. JClaw seeds
provider.ollama-local.baseUrl=http://localhost:11434/v1 at first boot,
so the provider is already listed in Settings without further wiring —
install Ollama on the host and pull a model to make it usable. A startup
probe logs INFO with the model count when the local server is reachable,
and WARN with an install hint when the server is reachable but broken;
a fresh install with no Ollama running stays silent (no spurious WARN
on every JVM start).
# Linux
curl https://ollama.com/install.sh | sh
# macOS
brew install ollama
# Windows — download the installer from https://ollama.com/download
After installing, pull a model:
ollama pull qwen2.5
Then open the Settings page, expand the ollama-local card, and either
run Discover Models against http://localhost:11434/v1 or paste the
JSON for the model you pulled into the models field. Bind an agent
to ollama-local from the Agent Edit page to start chatting against
your local model.
LM Studio — required only if you want to bind agents to the
lm-studio LLM provider. JClaw seeds
provider.lm-studio.baseUrl=http://localhost:1234/v1 at first boot
so the provider is already listed under "Local" in Settings. Same
boot-time probe as ollama-local: INFO when reachable, WARN with a
launch hint when reachable-but-broken, silent (DEBUG) when LM Studio
isn't running.
LM Studio is a desktop application — install it from https://lmstudio.ai (macOS DMG, Windows installer, or Linux AppImage). After launching, load a model in the My Models tab, then switch to the Server tab and click Start Server. The default port is 1234.
In JClaw Settings, expand the lm-studio card and either run
Discover Models against http://localhost:1234/v1 or paste the JSON
for the model you loaded into the models field. Bind an agent to
lm-studio from the Agent Edit page to use it.
git clone https://bitbucket.abundent.com/scm/jclaw/jclaw.git
cd jclaw
Dependencies are automatically installed when you start with jclaw.sh.
The fastest way to start coding without installing any of the Prerequisites on your host machine is to use the included dev container. The .devcontainer/Dockerfile ships a pinned toolchain (Java 25, Python 3.14, Node 24, corepack, the Play fork at the version recorded in .play-version, tesseract-ocr) on top of Ubuntu 26.04 LTS — all the prerequisites listed above, already installed.
Just two things on your machine:
After cloning, open the project in your IDE and trigger the "Reopen in Container" command:
| IDE | How to launch |
|---|---|
| Cursor | Cmd/Ctrl+Shift+P → Dev Containers: Reopen in Container |
| VS Code | Click the blue corner icon (bottom-left) → Reopen in Container, or Cmd/Ctrl+Shift+P → same command |
| GitHub Codespaces | Push your branch to GitHub → click Code → Codespaces tab → Create codespace on main |
| JetBrains Gateway | New Connection → Dev Containers → point at the local jclaw folder |
What happens automatically once you click:
.devcontainer/Dockerfile (~5–10 min the first time, cached on subsequent rebuilds)./workspaces/jclaw. Edits you make inside the container persist on your host — the container is an environment, not a copy.postCreateCommand: ./jclaw.sh setup automatically, which:
.githooks/pre-commit, .githooks/pre-push)pnpm install for the frontendgithub remote (https://github.com/tsukhani/jclaw.git)Everything works the same as it would on a native host. The container is a Linux dev box with the pre-installed toolchain:
./jclaw.sh --dev start # dev mode (Play autoreload + Nuxt HMR)
./jclaw.sh test # backend + frontend test suites
./jclaw.sh status # check what's running
./jclaw.sh stop
Ports 9000 (backend) and 3000 (Nuxt) are forwarded to your host automatically. Open http://localhost:9000 and http://localhost:3000 in your host's browser while the dev server runs inside the container. The Nuxt port is configured to auto-open the browser when it boots; the backend port emits a notification.
/deployThe pre-commit hook (frontend lint-staged) and pre-push hook (full test suite) work inside the container without any extra setup. Two nuances:
/deploy produces signed commits and signed tags (commit -S, tag -s). Your host's GPG/SSH keys aren't visible inside the container by default. Two recovery options:
/deploy from your host shell (open a host terminal, cd into the project, run the slash command). Code inside the container, deploy from outside.mounts block to .devcontainer/devcontainer.json to bind-mount ~/.ssh and ~/.gnupg into the container. Same end result, more configuration.ubuntu user). On macOS this maps to your user automatically; on Linux you may see "owned by 1000" in ls -l if your host UID differs. Usually harmless.When the toolchain changes (e.g., a new Play version, a JDK bump, a base-image bump), you'll want a fresh build:
| IDE | How to rebuild |
|---|---|
| Cursor / VS Code | Cmd/Ctrl+Shift+P → Dev Containers: Rebuild Container |
| JetBrains Gateway | Container settings → Rebuild |
| CLI fallback | docker build -t jclaw-devcontainer:latest .devcontainer/ (manual, you'd then need to update the IDE config to use the rebuilt image) |
Most rebuilds reuse cached apt + JDK + Node layers and only re-download what changed (e.g., the Play release zip if PLAY_VERSION was bumped). Full cold rebuilds run ~5–10 min.
sudo systemctl start docker../jclaw.sh setup — read the error; it'll point at the failing prereq. Open .devcontainer/Dockerfile to see what's installed; if a tool is missing, file an issue or patch the Dockerfile and rebuild.docker rmi to clean up — docker rmi jclaw-devcontainer:latest (or the container image name your IDE assigns) removes the cached image. The next "Reopen in Container" rebuilds from scratch.# Start both backend and frontend in dev mode
./jclaw.sh --dev start
# Stop
./jclaw.sh --dev stop
# Check status
./jclaw.sh --dev status
# View logs (tails both backend and frontend logs)
./jclaw.sh --dev logs
Default ports: backend on :9000, frontend on :3000.
For a turnkey production install, use the Quick Install (which downloads the self-contained jclaw-bundle.zip) or Docker. To build that bundle yourself, run ./jclaw.sh bundle — it produces a self-contained dist/jclaw-bundle.zip that runs with only a Java 25 JRE. Unzip it wherever you want to install JClaw, then start it in place:
# Start
./jclaw.sh start
# Stop
./jclaw.sh stop
# View logs
./jclaw.sh logs
The simplest way to run JClaw in production is with Docker Compose. The shipped docker-compose.yml pulls the prebuilt image from GHCR, publishes the app on :9000, and persists data/, logs/, workspace/, and skills/ to the host so config and conversations survive restarts.
# Start in the background
docker compose up -d
# Follow logs
docker compose logs -f
# Stop and remove the container
docker compose down
# Run on a custom port (default: 9000)
JCLAW_PORT=8080 docker compose up -d
That's it — no .env setup needed. On first boot the container's entrypoint generates a 64-character PLAY_SECRET (used to sign session cookies) and persists it to ./data/.play-secret. Subsequent restarts read the same file, so existing user sessions survive across docker compose down / up cycles. To rotate the secret, delete ./data/.play-secret and restart the container — all existing PLAY_SESSION cookies become invalid, which is the point.
If you'd rather pin the secret yourself (e.g. for multi-host deployments that need a shared cookie key, or rotation managed by your secret-store), drop a .env file alongside docker-compose.yml with PLAY_SECRET=<value> — Compose will forward it into the container and the entrypoint will defer to it instead of generating one.
You can also set JCLAW_PORT in .env alongside docker-compose.yml instead of passing it inline — Compose reads the same file for variable interpolation in the YAML and for the runtime environment of the jclaw service.
The container runs in production mode — the Nuxt SPA is already built into the image, so no local Node.js, pnpm, or Play toolchain is required on the host. Open http://localhost:9000 (or your custom port) once the container is healthy.
JClaw already sends the right cache headers, and a proxy that rewrites or ignores them is the one remaining way to serve a stale SPA. The app sends Cache-Control: no-cache on the HTML shell (so it always revalidates) and public, max-age=31536000, immutable on the content-hashed _nuxt/ chunks (so they never do). That split only works end-to-end if your proxy leaves it alone.
If you front JClaw with nginx, Caddy, Traefik, or a CDN:
index.html pointing at chunk hashes that no longer exist.Cache-Control through unmodified. Don't add a blanket expires or proxy_cache_valid covering all responses./ and /_nuxt/builds/. The build manifest under builds/ advertises the current build id; caching it defeats new-deploy detection. Everything else under _nuxt/ is content-hashed and safe to cache hard.Diagnosing a suspected stale SPA: curl -s localhost:9000/api/status | jq .spaBuildId reports the build id the server is actually serving. Compare it against the id the browser has (DevTools → Network → builds/latest.json). Matching ids mean the browser is current and the problem is elsewhere; differing ids mean a caching layer is holding an old shell.
location / {
proxy_pass http://127.0.0.1:9000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Let the app decide cacheability — it already differentiates the
# always-revalidate shell from the immutable hashed chunks.
proxy_cache off;
}
The container also exposes HTTPS on :9443 (with HTTP/3 over the same UDP port) using a self-signed TLS cert generated at certs/host.cert on first boot. HTTPS works as-is but browsers show a cert-warning interstitial, and Chrome refuses HTTP/3 entirely — QUIC requires the cert to be in the system trust store. To get browser-trusted HTTPS plus working HTTP/3, sign the cert with mkcert's local CA from your host. Install mkcert via your platform's package manager:
# macOS
brew install mkcert
# Debian / Ubuntu (22.04+)
sudo apt install mkcert libnss3-tools
# Fedora / RHEL
sudo dnf install mkcert nss-tools
# Arch
sudo pacman -S mkcert nss
# Windows
choco install mkcert
# or: scoop bucket add extras && scoop install mkcert
For older distros that don't package mkcert, grab the prebuilt binary from the mkcert releases page and install libnss3-tools (Debian/Ubuntu) or nss-tools (Fedora) separately so mkcert can register with Firefox.
Then trust the local CA, regenerate the cert, and restart the container:
sudo mkcert -install # adds mkcert's CA to the system trust store (and Firefox NSS if installed)
./jclaw.sh https # regenerates certs/host.cert + host.key, signed by the CA
docker compose restart jclaw # JVM reloads the new cert at boot
Subsequent docker compose up -d calls reuse the existing cert — you only need to re-run ./jclaw.sh https after rotating mkcert's CA or deleting the certs/ directory. Run ./jclaw.sh no-https to delete the cert+key (the next start boots HTTP/1.1 only). conf/application.conf is never modified by either command.
Use --backend-port and --frontend-port with any jclaw.sh mode. The frontend reads the backend port via the JCLAW_BACKEND_PORT environment variable at startup — no files are modified.
# Dev mode with custom ports
./jclaw.sh --dev --backend-port 8080 --frontend-port 4000 start
# Bare start with custom backend port
./jclaw.sh --backend-port 8080 start
Run the backend and frontend test suites together and print a consolidated pass/fail summary:
./jclaw.sh test
This runs play autotest (backend JUnit + functional tests), pnpm test (frontend Vitest), and the three frontend quality gates — stylelint, lint, and typecheck — streaming each check's output live, and finishes with a five-line verdict like:
backend : PASSED (47 classes, 26s)
frontend : PASSED Tests 199 passed (199) (5s)
stylelint: PASSED (3s)
lint : PASSED (4s)
typecheck: PASSED (8s)
Each check writes its full output to logs/test-<check>.log (e.g. logs/test-backend.log, logs/test-typecheck.log) for post-mortem on failure. The command exits non-zero if any check failed, so it's safe to wire into git hooks or CI.
Agent behaviour is measured against datasets in evals/suites/ — tool selection, structured output, and grounding, each a set of cases with deterministic pass criteria:
./jclaw.sh evals # validate the dataset
./jclaw.sh evals --responses run.json --out reports/now.json # score a recorded run
./jclaw.sh evals --responses run.json --baseline reports/last.json # catch regressions
Eval runs are offline — no backend, no model call, no database — and play autotest validates the dataset on every run, so a malformed suite fails the build. See evals/README.md for the format and for why a suite is edited in place, with a content fingerprint guarding comparability between runs.
./jclaw.sh setup wires the three in-repo hooks from .githooks/ once per clone. pre-commit runs lint-staged over staged frontend/** files; pre-push runs ./jclaw.sh test before a push reaches the remote and caches the tested SHA in $GIT_DIR/jclaw-last-tested-sha, so the second push in a two-remote deploy flow (origin + github) reuses the result instead of re-running the suite; post-checkout seeds a new worktree via ./jclaw.sh init-worktree. To wire them by hand instead:
git config core.hooksPath .githooks
To bypass for a one-off push (e.g. urgent hotfix, docs-only change): JCLAW_SKIP_TESTS=1 git push origin HEAD.
@Every / @OnApplicationStart job systemscheduled_tasks table with atomic row-claim, pluggable retries, and heartbeat-based dead-execution recoveryssr: false)class-variance-authority + tailwind-merge for variants@tailwindcss/vite), Lucide + Heroicons icons, Inter variable fontuseState (no Pinia); @vueuse/core utilities@tanstack/vue-table for tables, marked + dompurify for safe Markdown, zod for validation$fetch to the Play backend, proxied via Nitro in dev@nuxt/test-utils + jsdom, Playwright e2e, ESLint + Stylelint, vue-tsc typecheck, a11y via vue-axe/axe-coreJClaw is developed exclusively by the internal Abundent team. We do not accept external contributions: pull requests from outside the team will be closed without review, however good the code. This is a deliberate legal choice that keeps the project's chain of title unambiguous under its dual-licensing model — see CONTRIBUTING.md.
Bug reports and questions are very welcome via GitHub issues or support@abundent.com.
JClaw is source-available and dual-licensed:
JClaw is not open-source software as defined by the Open Source Definition, because the noncommercial license restricts the field of use. The source is public and free to read, study, and use noncommercially.
Historical versions: releases up to and including v0.15.4 were published under the MIT License and remain available under that license from the corresponding git tags. The dual-licensing model applies from v0.16.0 onward.
Built with ☕ Java and ❤️ by the Abundent crew.
3,823 commits
136 commits
Java
73.9%
TypeScript
10.7%
Vue
10.5%
Python
2.3%
Shell
1.7%
Java-based Enterprise AI Harness — AI-powered automation platform built in pure Java on Abundent's fork of the Play Framework 1.x with a Nuxt frontend. Combines agent orchestration and job scheduling with no Spring, no Node.js, and no Python runtime.
19
stars
3,959
commits
Java
primary language
Sep 10, 2026
updated
JAVA FIRST. NO BLOAT. PURE POWER.
Licensing at a glance. JClaw is source-available, dual-licensed by Abundent Sdn Bhd: free for noncommercial use under the PolyForm Noncommercial License 1.0.0; any commercial use requires a commercial license. JClaw is not open-source software. Versions through v0.15.4 remain available under their original MIT License.
Get JClaw running in one command. The installer downloads the self-contained
jclaw-bundle.zip from the latest GitHub Release, verifies Java 25+ (the bundle's
only runtime dependency), extracts it to ~/.jclaw, and starts JClaw on
http://localhost:9000.
macOS & Linux
curl -fsSL https://raw.githubusercontent.com/tsukhani/jclaw/main/install.sh | sh
Windows (PowerShell)
irm https://raw.githubusercontent.com/tsukhani/jclaw/main/install.ps1 | iex
On Windows the bundle runs through Git Bash or WSL (the launcher is a POSIX shell script). The installer prefers Git Bash; if only WSL is present it launches there; if neither is found it installs and prints how to run it.
Once running, manage it with jclaw status, jclaw stop, jclaw restart from
any new shell — the installer puts the jclaw command on your PATH (via
~/.local/bin) and wires up <TAB> completion for bash and zsh. To remove JClaw
entirely, run jclaw uninstall: it stops the app, undoes the PATH and completion
wiring, and deletes ~/.jclaw.
Updating: jclaw upgrade (or Settings → Upgrade in the app) installs the
newest release in place. Your database, workspace, credentials, installed apps and
edited configuration are carried across; the database is backed up first; and a
release that fails to start is rolled back automatically. The download runs while
JClaw keeps serving, so only the swap itself is downtime. jclaw upgrade --check
reports what's available without installing it. Re-running the one-line installer
does the same thing — it hands off to jclaw upgrade when an install already
exists. Docker deployments upgrade the image instead
(docker compose pull && docker compose up -d), and a git clone uses git pull.
Requirements: a Java 25+ runtime (Zulu or Temurin). Nothing else — the bundle bakes in the framework, app dependencies, precompiled classes, and the prebuilt SPA.
Configuration (optional environment variables):
| Variable | Default | Purpose |
|---|---|---|
JCLAW_HOME | ~/.jclaw | Install directory |
JCLAW_VERSION | latest | Pin a release tag, e.g. v0.14.7 |
JCLAW_PORT | 9000 | Port reported on launch |
JCLAW_NO_START | — | Set to 1 to install without starting |
JCLAW_BUNDLE_URL | — | Install from a specific bundle URL (including file://) instead of GitHub Releases |
# Pin a version and install without auto-starting:
curl -fsSL https://raw.githubusercontent.com/tsukhani/jclaw/main/install.sh \
| JCLAW_VERSION=v0.14.7 JCLAW_NO_START=1 sh
JClaw is Abundent's AI-powered automation platform, built from scratch in pure Java on a customized Play Framework 1.x foundation. It draws ideas and feature designs from three predecessor projects:
The implementation is entirely original — no code is shared with either project. JClaw is built on lean library primitives (OkHttp 5, db-scheduler, ProcessBuilder, virtual threads, JPA) with no Spring, no heavy framework bloat, no Python, and no Node.js runtime on the server. The result is a leaner, faster, more maintainable platform for building AI agents and automation workflows.
Web chat with memory-aware agents, tool execution, and markdown rendering.
jclaw/
├── app/ # Application code
│ ├── controllers/ # HTTP controllers (Play 1.x pattern)
│ ├── models/ # JPA domain entities
│ ├── services/ # Business logic (incl. db-scheduler bridge)
│ ├── agents/ # AI agent implementations
│ ├── channels/ # Messaging channels (web, Telegram, Slack)
│ ├── llm/ # LLM provider drivers (OkHttp 5)
│ ├── tools/ # Agent tool implementations
│ ├── memory/ # Agent memory stores (JPA-backed)
│ ├── mcp/ # Model Context Protocol client
│ ├── slash/ # Slash-command handlers
│ ├── jobs/ # Play @Every jobs + db-scheduler handlers
│ ├── views/ # Groovy server templates
│ └── utils/ # Utility classes
├── conf/ # Play configuration
│ ├── application.conf # Main app config
│ ├── routes # URL routing
│ ├── play.plugins # Play plugin registration
│ └── log4j2.xml # Logging configuration
├── frontend/ # Nuxt 4 SPA (SPA-only; ssr: false)
│ ├── app.vue # Root component
│ ├── layouts/ # Page layouts
│ ├── pages/ # Nuxt file-based routes
│ ├── components/ # Reusable Vue components
│ ├── composables/ # Shared reactive state (useAuth, useEventBus, ...)
│ ├── middleware/ # Global route middleware (auth guard)
│ ├── public/ # Static assets
│ └── nuxt.config.ts # Nuxt configuration
├── evals/ # Agent-behaviour eval datasets
├── lib/ # Custom JARs (if needed)
├── modules/ # Play modules (auto-managed)
├── public/ # Static web assets
├── test/ # Unit and integration tests
├── tmp/ # Play temp/runtime files
├── logs/ # Application logs
└── README.md # This file
That's the whole list. The Abundent Play 1.x fork,
app dependencies, precompiled classes, and the prebuilt SPA all ship inside
jclaw-bundle.zip, so a Java 25 runtime is the only thing the host needs to run
JClaw — see Quick Install. (Building from source instead
adds a dev toolchain — Node.js, pnpm, and the play CLI — which the
Dev Container installs for you.)
Tesseract OCR — required for text extraction from images, scanned PDFs,
and image-only PDFs via the documents tool. Apache Tika invokes the
tesseract binary as a subprocess; without it, image inputs return empty
text. A startup probe logs a WARN line at boot if tesseract is missing so
the missing capability is visible without trial and error.
# Debian / Ubuntu
sudo apt-get install tesseract-ocr
# macOS
brew install tesseract
# Windows
choco install tesseract
# or: winget install --id UB-Mannheim.TesseractOCR
Additional language packs install separately. The default is English
(eng); install tesseract-ocr-fra, tesseract-ocr-jpn, etc. for other
languages, then update ocr.tesseract.languages in conf/application.conf
(e.g. eng+fra+jpn).
Local Ollama — required only if you want to bind agents to the
ollama-local LLM provider for self-hosted inference. JClaw seeds
provider.ollama-local.baseUrl=http://localhost:11434/v1 at first boot,
so the provider is already listed in Settings without further wiring —
install Ollama on the host and pull a model to make it usable. A startup
probe logs INFO with the model count when the local server is reachable,
and WARN with an install hint when the server is reachable but broken;
a fresh install with no Ollama running stays silent (no spurious WARN
on every JVM start).
# Linux
curl https://ollama.com/install.sh | sh
# macOS
brew install ollama
# Windows — download the installer from https://ollama.com/download
After installing, pull a model:
ollama pull qwen2.5
Then open the Settings page, expand the ollama-local card, and either
run Discover Models against http://localhost:11434/v1 or paste the
JSON for the model you pulled into the models field. Bind an agent
to ollama-local from the Agent Edit page to start chatting against
your local model.
LM Studio — required only if you want to bind agents to the
lm-studio LLM provider. JClaw seeds
provider.lm-studio.baseUrl=http://localhost:1234/v1 at first boot
so the provider is already listed under "Local" in Settings. Same
boot-time probe as ollama-local: INFO when reachable, WARN with a
launch hint when reachable-but-broken, silent (DEBUG) when LM Studio
isn't running.
LM Studio is a desktop application — install it from https://lmstudio.ai (macOS DMG, Windows installer, or Linux AppImage). After launching, load a model in the My Models tab, then switch to the Server tab and click Start Server. The default port is 1234.
In JClaw Settings, expand the lm-studio card and either run
Discover Models against http://localhost:1234/v1 or paste the JSON
for the model you loaded into the models field. Bind an agent to
lm-studio from the Agent Edit page to use it.
git clone https://bitbucket.abundent.com/scm/jclaw/jclaw.git
cd jclaw
Dependencies are automatically installed when you start with jclaw.sh.
The fastest way to start coding without installing any of the Prerequisites on your host machine is to use the included dev container. The .devcontainer/Dockerfile ships a pinned toolchain (Java 25, Python 3.14, Node 24, corepack, the Play fork at the version recorded in .play-version, tesseract-ocr) on top of Ubuntu 26.04 LTS — all the prerequisites listed above, already installed.
Just two things on your machine:
After cloning, open the project in your IDE and trigger the "Reopen in Container" command:
| IDE | How to launch |
|---|---|
| Cursor | Cmd/Ctrl+Shift+P → Dev Containers: Reopen in Container |
| VS Code | Click the blue corner icon (bottom-left) → Reopen in Container, or Cmd/Ctrl+Shift+P → same command |
| GitHub Codespaces | Push your branch to GitHub → click Code → Codespaces tab → Create codespace on main |
| JetBrains Gateway | New Connection → Dev Containers → point at the local jclaw folder |
What happens automatically once you click:
.devcontainer/Dockerfile (~5–10 min the first time, cached on subsequent rebuilds)./workspaces/jclaw. Edits you make inside the container persist on your host — the container is an environment, not a copy.postCreateCommand: ./jclaw.sh setup automatically, which:
.githooks/pre-commit, .githooks/pre-push)pnpm install for the frontendgithub remote (https://github.com/tsukhani/jclaw.git)Everything works the same as it would on a native host. The container is a Linux dev box with the pre-installed toolchain:
./jclaw.sh --dev start # dev mode (Play autoreload + Nuxt HMR)
./jclaw.sh test # backend + frontend test suites
./jclaw.sh status # check what's running
./jclaw.sh stop
Ports 9000 (backend) and 3000 (Nuxt) are forwarded to your host automatically. Open http://localhost:9000 and http://localhost:3000 in your host's browser while the dev server runs inside the container. The Nuxt port is configured to auto-open the browser when it boots; the backend port emits a notification.
/deployThe pre-commit hook (frontend lint-staged) and pre-push hook (full test suite) work inside the container without any extra setup. Two nuances:
/deploy produces signed commits and signed tags (commit -S, tag -s). Your host's GPG/SSH keys aren't visible inside the container by default. Two recovery options:
/deploy from your host shell (open a host terminal, cd into the project, run the slash command). Code inside the container, deploy from outside.mounts block to .devcontainer/devcontainer.json to bind-mount ~/.ssh and ~/.gnupg into the container. Same end result, more configuration.ubuntu user). On macOS this maps to your user automatically; on Linux you may see "owned by 1000" in ls -l if your host UID differs. Usually harmless.When the toolchain changes (e.g., a new Play version, a JDK bump, a base-image bump), you'll want a fresh build:
| IDE | How to rebuild |
|---|---|
| Cursor / VS Code | Cmd/Ctrl+Shift+P → Dev Containers: Rebuild Container |
| JetBrains Gateway | Container settings → Rebuild |
| CLI fallback | docker build -t jclaw-devcontainer:latest .devcontainer/ (manual, you'd then need to update the IDE config to use the rebuilt image) |
Most rebuilds reuse cached apt + JDK + Node layers and only re-download what changed (e.g., the Play release zip if PLAY_VERSION was bumped). Full cold rebuilds run ~5–10 min.
sudo systemctl start docker../jclaw.sh setup — read the error; it'll point at the failing prereq. Open .devcontainer/Dockerfile to see what's installed; if a tool is missing, file an issue or patch the Dockerfile and rebuild.docker rmi to clean up — docker rmi jclaw-devcontainer:latest (or the container image name your IDE assigns) removes the cached image. The next "Reopen in Container" rebuilds from scratch.# Start both backend and frontend in dev mode
./jclaw.sh --dev start
# Stop
./jclaw.sh --dev stop
# Check status
./jclaw.sh --dev status
# View logs (tails both backend and frontend logs)
./jclaw.sh --dev logs
Default ports: backend on :9000, frontend on :3000.
For a turnkey production install, use the Quick Install (which downloads the self-contained jclaw-bundle.zip) or Docker. To build that bundle yourself, run ./jclaw.sh bundle — it produces a self-contained dist/jclaw-bundle.zip that runs with only a Java 25 JRE. Unzip it wherever you want to install JClaw, then start it in place:
# Start
./jclaw.sh start
# Stop
./jclaw.sh stop
# View logs
./jclaw.sh logs
The simplest way to run JClaw in production is with Docker Compose. The shipped docker-compose.yml pulls the prebuilt image from GHCR, publishes the app on :9000, and persists data/, logs/, workspace/, and skills/ to the host so config and conversations survive restarts.
# Start in the background
docker compose up -d
# Follow logs
docker compose logs -f
# Stop and remove the container
docker compose down
# Run on a custom port (default: 9000)
JCLAW_PORT=8080 docker compose up -d
That's it — no .env setup needed. On first boot the container's entrypoint generates a 64-character PLAY_SECRET (used to sign session cookies) and persists it to ./data/.play-secret. Subsequent restarts read the same file, so existing user sessions survive across docker compose down / up cycles. To rotate the secret, delete ./data/.play-secret and restart the container — all existing PLAY_SESSION cookies become invalid, which is the point.
If you'd rather pin the secret yourself (e.g. for multi-host deployments that need a shared cookie key, or rotation managed by your secret-store), drop a .env file alongside docker-compose.yml with PLAY_SECRET=<value> — Compose will forward it into the container and the entrypoint will defer to it instead of generating one.
You can also set JCLAW_PORT in .env alongside docker-compose.yml instead of passing it inline — Compose reads the same file for variable interpolation in the YAML and for the runtime environment of the jclaw service.
The container runs in production mode — the Nuxt SPA is already built into the image, so no local Node.js, pnpm, or Play toolchain is required on the host. Open http://localhost:9000 (or your custom port) once the container is healthy.
JClaw already sends the right cache headers, and a proxy that rewrites or ignores them is the one remaining way to serve a stale SPA. The app sends Cache-Control: no-cache on the HTML shell (so it always revalidates) and public, max-age=31536000, immutable on the content-hashed _nuxt/ chunks (so they never do). That split only works end-to-end if your proxy leaves it alone.
If you front JClaw with nginx, Caddy, Traefik, or a CDN:
index.html pointing at chunk hashes that no longer exist.Cache-Control through unmodified. Don't add a blanket expires or proxy_cache_valid covering all responses./ and /_nuxt/builds/. The build manifest under builds/ advertises the current build id; caching it defeats new-deploy detection. Everything else under _nuxt/ is content-hashed and safe to cache hard.Diagnosing a suspected stale SPA: curl -s localhost:9000/api/status | jq .spaBuildId reports the build id the server is actually serving. Compare it against the id the browser has (DevTools → Network → builds/latest.json). Matching ids mean the browser is current and the problem is elsewhere; differing ids mean a caching layer is holding an old shell.
location / {
proxy_pass http://127.0.0.1:9000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Let the app decide cacheability — it already differentiates the
# always-revalidate shell from the immutable hashed chunks.
proxy_cache off;
}
The container also exposes HTTPS on :9443 (with HTTP/3 over the same UDP port) using a self-signed TLS cert generated at certs/host.cert on first boot. HTTPS works as-is but browsers show a cert-warning interstitial, and Chrome refuses HTTP/3 entirely — QUIC requires the cert to be in the system trust store. To get browser-trusted HTTPS plus working HTTP/3, sign the cert with mkcert's local CA from your host. Install mkcert via your platform's package manager:
# macOS
brew install mkcert
# Debian / Ubuntu (22.04+)
sudo apt install mkcert libnss3-tools
# Fedora / RHEL
sudo dnf install mkcert nss-tools
# Arch
sudo pacman -S mkcert nss
# Windows
choco install mkcert
# or: scoop bucket add extras && scoop install mkcert
For older distros that don't package mkcert, grab the prebuilt binary from the mkcert releases page and install libnss3-tools (Debian/Ubuntu) or nss-tools (Fedora) separately so mkcert can register with Firefox.
Then trust the local CA, regenerate the cert, and restart the container:
sudo mkcert -install # adds mkcert's CA to the system trust store (and Firefox NSS if installed)
./jclaw.sh https # regenerates certs/host.cert + host.key, signed by the CA
docker compose restart jclaw # JVM reloads the new cert at boot
Subsequent docker compose up -d calls reuse the existing cert — you only need to re-run ./jclaw.sh https after rotating mkcert's CA or deleting the certs/ directory. Run ./jclaw.sh no-https to delete the cert+key (the next start boots HTTP/1.1 only). conf/application.conf is never modified by either command.
Use --backend-port and --frontend-port with any jclaw.sh mode. The frontend reads the backend port via the JCLAW_BACKEND_PORT environment variable at startup — no files are modified.
# Dev mode with custom ports
./jclaw.sh --dev --backend-port 8080 --frontend-port 4000 start
# Bare start with custom backend port
./jclaw.sh --backend-port 8080 start
Run the backend and frontend test suites together and print a consolidated pass/fail summary:
./jclaw.sh test
This runs play autotest (backend JUnit + functional tests), pnpm test (frontend Vitest), and the three frontend quality gates — stylelint, lint, and typecheck — streaming each check's output live, and finishes with a five-line verdict like:
backend : PASSED (47 classes, 26s)
frontend : PASSED Tests 199 passed (199) (5s)
stylelint: PASSED (3s)
lint : PASSED (4s)
typecheck: PASSED (8s)
Each check writes its full output to logs/test-<check>.log (e.g. logs/test-backend.log, logs/test-typecheck.log) for post-mortem on failure. The command exits non-zero if any check failed, so it's safe to wire into git hooks or CI.
Agent behaviour is measured against datasets in evals/suites/ — tool selection, structured output, and grounding, each a set of cases with deterministic pass criteria:
./jclaw.sh evals # validate the dataset
./jclaw.sh evals --responses run.json --out reports/now.json # score a recorded run
./jclaw.sh evals --responses run.json --baseline reports/last.json # catch regressions
Eval runs are offline — no backend, no model call, no database — and play autotest validates the dataset on every run, so a malformed suite fails the build. See evals/README.md for the format and for why a suite is edited in place, with a content fingerprint guarding comparability between runs.
./jclaw.sh setup wires the three in-repo hooks from .githooks/ once per clone. pre-commit runs lint-staged over staged frontend/** files; pre-push runs ./jclaw.sh test before a push reaches the remote and caches the tested SHA in $GIT_DIR/jclaw-last-tested-sha, so the second push in a two-remote deploy flow (origin + github) reuses the result instead of re-running the suite; post-checkout seeds a new worktree via ./jclaw.sh init-worktree. To wire them by hand instead:
git config core.hooksPath .githooks
To bypass for a one-off push (e.g. urgent hotfix, docs-only change): JCLAW_SKIP_TESTS=1 git push origin HEAD.
@Every / @OnApplicationStart job systemscheduled_tasks table with atomic row-claim, pluggable retries, and heartbeat-based dead-execution recoveryssr: false)class-variance-authority + tailwind-merge for variants@tailwindcss/vite), Lucide + Heroicons icons, Inter variable fontuseState (no Pinia); @vueuse/core utilities@tanstack/vue-table for tables, marked + dompurify for safe Markdown, zod for validation$fetch to the Play backend, proxied via Nitro in dev@nuxt/test-utils + jsdom, Playwright e2e, ESLint + Stylelint, vue-tsc typecheck, a11y via vue-axe/axe-coreJClaw is developed exclusively by the internal Abundent team. We do not accept external contributions: pull requests from outside the team will be closed without review, however good the code. This is a deliberate legal choice that keeps the project's chain of title unambiguous under its dual-licensing model — see CONTRIBUTING.md.
Bug reports and questions are very welcome via GitHub issues or support@abundent.com.
JClaw is source-available and dual-licensed:
JClaw is not open-source software as defined by the Open Source Definition, because the noncommercial license restricts the field of use. The source is public and free to read, study, and use noncommercially.
Historical versions: releases up to and including v0.15.4 were published under the MIT License and remain available under that license from the corresponding git tags. The dual-licensing model applies from v0.16.0 onward.
Built with ☕ Java and ❤️ by the Abundent crew.
3,823 commits
136 commits
Java
73.9%
TypeScript
10.7%
Vue
10.5%
Python
2.3%
Shell
1.7%