5.9 KiB
Kotlin Script Host (Gradle Project)
This project provides two runtime entrypoints while keeping dynamic script loading from scripts/.
Run CLI host
cd /tmp/kotlin-scripts
./gradlew runCli --args='scripts/hello.hub.kts'
./gradlew runCli --args='scripts/hello.hub.kts --arg=name=Codex --arg=upper=true'
Watch mode:
./gradlew runCli --args='scripts/hello.hub.kts --watch --debounce-ms=200'
Run Web host (Ktor)
./gradlew runWeb --args='--port=8080 --scripts-dir=./scripts'
Auth:
- Use
Authorization: Bearer <token>for all APIs except/health. - Token source:
- Preferred: set env
HOST_API_TOKEN. - Otherwise host auto-generates a token and stores it at
scripts/.host-api-token.
- Preferred: set env
Routes:
GET /healthGET /scriptsGET /scripts/{script}(raw script content)POST /scripts/{script}PUT /scripts/{script}DELETE /scripts/{script}GET /meta/{script}GET /run/{script}?k=vPOST /run/{script}?k=v
Examples:
curl 'http://127.0.0.1:8080/health'
TOKEN="$(cat scripts/.host-api-token)"
curl -H "Authorization: Bearer $TOKEN" 'http://127.0.0.1:8080/scripts'
curl -H "Authorization: Bearer $TOKEN" 'http://127.0.0.1:8080/scripts/hello'
curl -H "Authorization: Bearer $TOKEN" -X POST 'http://127.0.0.1:8080/scripts/new-api' --data-binary $'// @desc: new api\nval args: Array<String> = emptyArray()\nprintln("ok")'
curl -H "Authorization: Bearer $TOKEN" -X PUT 'http://127.0.0.1:8080/scripts/new-api' --data-binary $'// @desc: new api v2\nval args: Array<String> = emptyArray()\nprintln("ok-v2")'
curl -H "Authorization: Bearer $TOKEN" -X DELETE 'http://127.0.0.1:8080/scripts/new-api'
curl -H "Authorization: Bearer $TOKEN" 'http://127.0.0.1:8080/meta/hello'
curl -H "Authorization: Bearer $TOKEN" 'http://127.0.0.1:8080/run/hello?name=Alice&upper=true'
curl -H "Authorization: Bearer $TOKEN" -X POST 'http://127.0.0.1:8080/run/hello?name=Alice' -d 'from-body'
Script Metadata & Args (*.hub.kts)
Scripts declare metadata in comments and receive request arguments through explicit args declaration:
// @desc: Demo greeting API
// @param: name | default=world | desc=Name to greet
// @param: token | required=true | desc=Required token
val args: Array<String> = emptyArray()
val kv = args.mapNotNull {
val i = it.indexOf('=')
if (i <= 0) null else it.substring(0, i) to it.substring(i + 1)
}.toMap()
val name = kv["name"] ?: "world"
val token = kv["token"] ?: error("token required")
println("hello $name, token=$token")
Dynamic scripts
You can add/remove *.hub.kts files in scripts/ at any time. The web host resolves scripts by route name (/run/{script} -> scripts/{script}.hub.kts) on each request, so newly added scripts are available immediately.
Notes
- This keeps runtime behavior dynamic; Gradle is used for dependency resolution and launching, not for precompiling scripts.
- IDE completion for regular Kotlin sources (
src/main/kotlin) is fully modelled by Gradle. - You do not need a package/build artifact step before each run.
runCliandrunWeblaunch directly from source; scripts are compiled on-demand per execution/request. - For script files with custom extension (
*.hub.kts), IDEA code insight is usually weaker than standard*.main.ktsor module Kotlin sources. This is an IDE limitation for custom script definitions.
Command CLI
A standalone CLI script is available at tools/api-cli.main.kts (independent from host internals, only HTTP calls).
Examples:
kotlin tools/api-cli.main.kts --base-url=http://127.0.0.1:8080 --token-file=./scripts/.host-api-token list
kotlin tools/api-cli.main.kts --token-file=./scripts/.host-api-token show hello
kotlin tools/api-cli.main.kts --token-file=./scripts/.host-api-token run hello --arg=name=Alice --arg=upper=true
kotlin tools/api-cli.main.kts --token-file=./scripts/.host-api-token create demo --text='// @desc: demo\nval args: Array<String> = emptyArray()\nprintln("ok")'
Note:
- In this environment,
elide run <kts> -- <args...>currently does not expose Kotlin script args reliably; usekotlinto run the CLI script.
Simple TUI
A minimal keyboard-driven TUI is available at tools/api-tui.main.kts.
Run:
kotlin tools/api-tui.main.kts --base-url=http://127.0.0.1:8080 --token-file=./scripts/.host-api-token
Keys:
Up/Downorj/k: switch scriptLeft/Rightorh/l: switch action (Refresh/Show/Run/Meta/Create/Edit/Delete/Quit)Enter: execute selected actionq: quit
Create/Edit/Delete behavior:
Create: prompt script name, then choose source mode:e(default): create temp file, open terminal editor, then upload via APIf: read a specified local file and upload via API- In editor mode, if content is unchanged from initial template, creation is cancelled
Edit: fetch current script content (GET /scripts/{script}), write to temp file, open editor, save+exit, then upload viaPUTDelete: asks confirmation before callingDELETERun: prompts for optional query args (k=v, separated by&or space), and optional POST mode/body- Now uses a keyboard-driven sub-menu (
Method/Query/Body/Execute/Cancel) and remembers last run config per script during the session
- Now uses a keyboard-driven sub-menu (
Editor selection:
- First uses
$EDITOR - Fallback to first available of
nvim,vim,nano
Docker
Build image:
docker build -t slhaf-hub:latest .
Run container (mount local scripts directory):
docker run --rm -p 8080:8080 \
-v /tmp/kotlin-scripts/scripts:/app/scripts \
-e HOST_API_TOKEN=your-token \
slhaf-hub:latest
Then call APIs:
curl http://127.0.0.1:8080/health
curl -H "Authorization: Bearer your-token" http://127.0.0.1:8080/scripts
Docker Compose
Run with compose:
# optional: export HOST_API_TOKEN=your-token
# optional: export HOST_PORT=8080
docker compose up -d --build
Check status/logs:
docker compose ps
docker compose logs -f slhaf-hub
Stop:
docker compose down