docs: rewrite README and add Chinese docs (README.zh-CN.md)

This commit is contained in:
2026-02-24 23:13:56 +08:00
parent 2c1b515326
commit 8a3ceb5bdb
2 changed files with 313 additions and 154 deletions

297
README.md
View File

@@ -1,167 +1,43 @@
# Kotlin Script Host (Gradle Project) # slhaf-hub
This project provides two runtime entrypoints while keeping dynamic script loading from `scripts/`. Kotlin-based dynamic script host with HTTP APIs, root/sub token auth, script metadata, timeout control, and companion CLI/TUI tools.
## Run CLI host Language:
- English: `README.md`
- 中文: `README.zh-CN.md`
## Features
- Dynamic script loading from `scripts/*.hub.kts` without restarting host
- Root/Sub token authorization model
- Metadata in script comments (`@desc`, `@timeout`, `@param`)
- Script CRUD + run + metadata APIs
- Subtoken management APIs
- Run concurrency limit (`--max-run-concurrency`)
- Script timeout (default 10s, script-level override)
## Requirements
- JDK 17+
- Gradle (or Gradle wrapper)
## Quick Start
### Server
#### 1) Run from terminal (Gradle)
```bash ```bash
cd /tmp/kotlin-scripts 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:
```bash
./gradlew runCli --args='scripts/hello.hub.kts --watch --debounce-ms=200'
```
## Run Web host (Ktor)
```bash
./gradlew runWeb --args='--host=0.0.0.0 --port=8080 --scripts-dir=./scripts' ./gradlew runWeb --args='--host=0.0.0.0 --port=8080 --scripts-dir=./scripts'
``` ```
Auth: #### 2) Run with Docker
- 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`.
- Token types:
- `root`: full access to all endpoints.
- `sub`: only `health`, filtered `scripts`, and allowed-script `meta`/`run`.
Routes:
- `GET /health`
- `GET /type`
- `GET /scripts`
- `GET /scripts/{script}` (raw script content)
- `POST /scripts/{script}`
- `PUT /scripts/{script}`
- `DELETE /scripts/{script}`
- `GET /meta/{script}`
- `GET /run/{script}?k=v`
- `POST /run/{script}?k=v`
- `GET /subtokens` (root only)
- `GET /subtokens/{name}` (root only)
- `POST /subtokens/{name}` (root only, body = script names list)
- `PUT /subtokens/{name}` (root only, body = script names list)
- `DELETE /subtokens/{name}` (root only)
Examples:
```bash
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/type'
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'
curl -H "Authorization: Bearer $TOKEN" -X POST 'http://127.0.0.1:8080/subtokens/demo-sub' --data-binary $'hello\ntime'
curl -H "Authorization: Bearer $TOKEN" 'http://127.0.0.1:8080/subtokens'
```
## Script Metadata & Args (`*.hub.kts`)
Scripts declare metadata in comments and receive request arguments through explicit `args` declaration:
```kotlin
// @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. `runCli` and `runWeb` launch 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.kts` or module Kotlin sources. This is an IDE limitation for custom script definitions.
## Command CLI
A standalone CLI script is available at `tools/slhaf-hub-cli.kts` (independent from host internals, only HTTP calls).
Examples:
```bash
kotlin tools/slhaf-hub-cli.kts --base-url=http://127.0.0.1:8080 --token-file=./scripts/.host-api-token list
kotlin tools/slhaf-hub-cli.kts --token-file=./scripts/.host-api-token type
kotlin tools/slhaf-hub-cli.kts --token-file=./scripts/.host-api-token show hello
kotlin tools/slhaf-hub-cli.kts --token-file=./scripts/.host-api-token run hello --arg=name=Alice --arg=upper=true
kotlin tools/slhaf-hub-cli.kts --token-file=./scripts/.host-api-token create demo --text='// @desc: demo\nval args: Array<String> = emptyArray()\nprintln("ok")'
kotlin tools/slhaf-hub-cli.kts --token-file=./scripts/.host-api-token sub-create demo-sub --scripts=hello,time
kotlin tools/slhaf-hub-cli.kts --token-file=./scripts/.host-api-token sub-list
```
Note:
- In this environment, `elide run <kts> -- <args...>` currently does not expose Kotlin script args reliably; use `kotlin` to run the CLI script.
## Simple TUI
A minimal keyboard-driven TUI is available at `tools/slhaf-hub-tui.kts`.
Run:
```bash
kotlin tools/slhaf-hub-tui.kts --base-url=http://127.0.0.1:8080 --token-file=./scripts/.host-api-token
```
Keys:
- `Up/Down` or `j/k`: switch script
- `Left/Right` or `h/l`: switch action (grouped by `Script` / `SubToken` / `System`)
- `Enter`: execute selected action
- `q`: quit
Action model:
- `root` token: full grouped actions including `Subtokens`
- `sub` token: reduced action set (`Refresh/Run/Meta/Type/Quit`)
- `Subtokens` opens a keyboard sub-menu (`List/Show/Create/Update/Delete/Back`)
Create/Edit/Delete behavior:
- `Create`: prompt script name, then choose source mode:
- `e` (default): create temp file, open terminal editor, then upload via API
- `f`: 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 via `PUT`
- `Delete`: asks confirmation before calling `DELETE`
- `Run`: 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
Editor selection:
- First uses `$EDITOR`
- Fallback to first available of `nvim`, `vim`, `nano`
## Docker
Build image:
```bash ```bash
docker build -t slhaf-hub:latest . docker build -t slhaf-hub:latest .
```
Run container (mount local scripts directory):
```bash
docker run --rm -p 8080:8080 \ docker run --rm -p 8080:8080 \
-v /tmp/kotlin-scripts/scripts:/app/scripts \ -v /tmp/kotlin-scripts/scripts:/app/scripts \
-e HOST_API_TOKEN=your-token \ -e HOST_API_TOKEN=your-token \
-e MAX_RUN_CONCURRENCY=8 \
slhaf-hub:latest slhaf-hub:latest
``` ```
Then call APIs: #### 3) Run with Docker Compose
```bash
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:
```bash ```bash
# optional: export HOST_API_TOKEN=your-token # optional: export HOST_API_TOKEN=your-token
# optional: export HOST_PORT=8080 # optional: export HOST_PORT=8080
@@ -169,13 +45,126 @@ Run with compose:
docker compose up -d --build docker compose up -d --build
``` ```
Check status/logs: Health check:
```bash ```bash
docker compose ps curl http://127.0.0.1:8080/health
docker compose logs -f slhaf-hub
``` ```
Stop: ### Clients
#### CLI
```bash ```bash
docker compose down kotlin tools/slhaf-hub-cli.kts --base-url=http://127.0.0.1:8080 --token-file=./scripts/.host-api-token list
kotlin tools/slhaf-hub-cli.kts --token-file=./scripts/.host-api-token type
kotlin tools/slhaf-hub-cli.kts --token-file=./scripts/.host-api-token run hello --arg=name=Alice --arg=upper=true
``` ```
#### TUI
```bash
kotlin tools/slhaf-hub-tui.kts --base-url=http://127.0.0.1:8080 --token-file=./scripts/.host-api-token
```
CLI/TUI env vars:
- `SLHAF_HUB_BASE_URL`
- `SLHAF_HUB_TOKEN`
- `SLHAF_HUB_TOKEN_FILE`
## Auth Model
Auth headers:
- `Authorization: Bearer <token>` (recommended)
- `X-Host-Token: <token>`
Token source priority:
1. `HOST_API_TOKEN` env var
2. `scripts/.host-api-token`
3. Auto-generated token saved to `scripts/.host-api-token`
Token types:
- `root`: full access
- `sub`: access to `/health`, `/type`, filtered `/scripts`, and allowed-script `/meta/{script}` + `/run/{script}`
## Script Metadata (`*.hub.kts`)
Example:
```kotlin
// @desc: Demo greeting API
// @timeout: 10s
// @param: name | required=false | default=world | desc=Name to greet
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()
println("hello " + (kv["name"] ?: "world"))
```
Fields:
- `@desc: <text>`
- `@timeout: <value>`
- `@param: <name> | required=true|false | default=<value> | desc=<text>`
Timeout formats:
- `500ms`, `10s`, `1m`, or plain integer seconds (`10`)
Default timeout:
- `10s`
### Metadata Validation
`POST /scripts/{script}` and `PUT /scripts/{script}` validate metadata before saving.
On validation failure, server returns `400 Bad Request` with:
- line-based reason details
- valid metadata examples
## API Summary
Public:
- `GET /health`
Authenticated:
- `GET /type`
- `GET /scripts`
- `GET /meta/{script}`
- `GET /run/{script}?k=v`
- `POST /run/{script}?k=v`
Root only:
- `GET /scripts/{script}`
- `POST /scripts/{script}`
- `PUT /scripts/{script}`
- `DELETE /scripts/{script}`
- `GET /subtokens`
- `GET /subtokens/{name}`
- `POST /subtokens/{name}`
- `PUT /subtokens/{name}`
- `DELETE /subtokens/{name}`
Common statuses:
- `200`, `201`, `400`, `401`, `403`, `404`, `408` (timeout)
## Runtime Controls
Run concurrency:
- arg: `--max-run-concurrency=<N>`
- env (compose): `MAX_RUN_CONCURRENCY`
- default: number of available processors
The limit only applies to `/run/*` endpoints.
## Testing
Run:
```bash
./gradlew test
```
Current automated coverage focuses on WebHost APIs:
- auth behavior
- script CRUD/meta/run
- metadata validation responses
- subtoken permission filtering
- run timeout behavior
## Project Layout
- `src/main/kotlin/work/slhaf/hub`: host implementation
- `src/test/kotlin/work/slhaf/hub`: automated tests
- `scripts`: runtime scripts and token/subtoken storage
- `tools`: standalone CLI/TUI scripts
- `Dockerfile`, `docker-compose.yml`: container deployment

170
README.zh-CN.md Normal file
View File

@@ -0,0 +1,170 @@
# slhaf-hub
基于 Kotlin 的动态脚本宿主,提供 HTTP API、root/sub token 鉴权、脚本 metadata、超时控制以及配套 CLI/TUI 工具。
语言版本:
- English: `README.md`
- 中文: `README.zh-CN.md`
## 功能概览
-`scripts/*.hub.kts` 动态加载脚本,无需重启 host
- Root/Sub token 鉴权模型
- 脚本注释 metadata`@desc``@timeout``@param`
- 脚本 CRUD + run + meta API
- subtoken 管理 API
- 运行并发限制(`--max-run-concurrency`
- 脚本执行超时(默认 10 秒,可脚本级覆盖)
## 环境要求
- JDK 17+
- Gradle或 Gradle Wrapper
## 快速启动
### 服务端
#### 1) 终端启动Gradle
```bash
cd /tmp/kotlin-scripts
./gradlew runWeb --args='--host=0.0.0.0 --port=8080 --scripts-dir=./scripts'
```
#### 2) Docker 启动
```bash
docker build -t slhaf-hub:latest .
docker run --rm -p 8080:8080 \
-v /tmp/kotlin-scripts/scripts:/app/scripts \
-e HOST_API_TOKEN=your-token \
-e MAX_RUN_CONCURRENCY=8 \
slhaf-hub:latest
```
#### 3) Docker Compose 启动
```bash
# 可选export HOST_API_TOKEN=your-token
# 可选export HOST_PORT=8080
# 可选export MAX_RUN_CONCURRENCY=8
docker compose up -d --build
```
健康检查:
```bash
curl http://127.0.0.1:8080/health
```
### 客户端
#### CLI
```bash
kotlin tools/slhaf-hub-cli.kts --base-url=http://127.0.0.1:8080 --token-file=./scripts/.host-api-token list
kotlin tools/slhaf-hub-cli.kts --token-file=./scripts/.host-api-token type
kotlin tools/slhaf-hub-cli.kts --token-file=./scripts/.host-api-token run hello --arg=name=Alice --arg=upper=true
```
#### TUI
```bash
kotlin tools/slhaf-hub-tui.kts --base-url=http://127.0.0.1:8080 --token-file=./scripts/.host-api-token
```
CLI/TUI 环境变量:
- `SLHAF_HUB_BASE_URL`
- `SLHAF_HUB_TOKEN`
- `SLHAF_HUB_TOKEN_FILE`
## 鉴权模型
鉴权请求头:
- `Authorization: Bearer <token>`(推荐)
- `X-Host-Token: <token>`
Token 来源优先级:
1. 环境变量 `HOST_API_TOKEN`
2. `scripts/.host-api-token`
3. 自动生成并写入 `scripts/.host-api-token`
Token 类型:
- `root`:完整权限
- `sub`:可访问 `/health``/type`、过滤后的 `/scripts`,以及被授权脚本的 `/meta/{script}``/run/{script}`
## 脚本 Metadata`*.hub.kts`
示例:
```kotlin
// @desc: Demo greeting API
// @timeout: 10s
// @param: name | required=false | default=world | desc=Name to greet
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()
println("hello " + (kv["name"] ?: "world"))
```
字段:
- `@desc: <text>`
- `@timeout: <value>`
- `@param: <name> | required=true|false | default=<value> | desc=<text>`
超时格式:
- `500ms``10s``1m`,或纯数字秒(`10`
默认超时:
- `10s`
### Metadata 校验
`POST /scripts/{script}``PUT /scripts/{script}` 在保存前会校验 metadata。
校验失败时返回 `400 Bad Request`,并包含:
- 行级错误原因
- 正确格式示例
## API 概览
公开接口:
- `GET /health`
鉴权后接口:
- `GET /type`
- `GET /scripts`
- `GET /meta/{script}`
- `GET /run/{script}?k=v`
- `POST /run/{script}?k=v`
仅 root
- `GET /scripts/{script}`
- `POST /scripts/{script}`
- `PUT /scripts/{script}`
- `DELETE /scripts/{script}`
- `GET /subtokens`
- `GET /subtokens/{name}`
- `POST /subtokens/{name}`
- `PUT /subtokens/{name}`
- `DELETE /subtokens/{name}`
常见状态码:
- `200``201``400``401``403``404``408`(超时)
## 运行控制
并发限制:
- 启动参数:`--max-run-concurrency=<N>`
- Compose 环境变量:`MAX_RUN_CONCURRENCY`
- 默认值:可用处理器数量
并发限制仅作用于 `/run/*` 接口。
## 自动化测试
执行:
```bash
./gradlew test
```
当前自动化覆盖 WebHost 接口:
- 鉴权行为
- 脚本 CRUD/meta/run
- metadata 校验返回
- subtoken 权限过滤
- run 超时行为
## 目录结构
- `src/main/kotlin/work/slhaf/hub`host 实现
- `src/test/kotlin/work/slhaf/hub`:自动化测试
- `scripts`:运行时脚本和 token/subtoken 存储
- `tools`:独立 CLI/TUI 脚本
- `Dockerfile``docker-compose.yml`:容器部署