diff --git a/README.md b/README.md index 25d4c88..a41e7d8 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ curl http://localhost:8080/health - Запускаемые: - [`:standalone`](standalone/README.md) — single-jar HTTP-сервер. - - [`:agentik-cli`](agentik-cli/README.md) — one-shot CLI-клиент (kotlinx.cli). + - [`:agentik-cli`](agentik-cli/README.md) — one-shot CLI-клиент (kotlinx.cli), JVM + 4 native. - Библиотеки (контракты и реализации): - [`:proto`](proto/README.md) — stateful KMP-протокол. - [`:server`](server/README.md) — HTTP/SSE фасад `:proto`. diff --git a/agentik-cli/README.md b/agentik-cli/README.md index e110b10..b2d4ca6 100644 --- a/agentik-cli/README.md +++ b/agentik-cli/README.md @@ -1,14 +1,26 @@ -# `:agentik-cli` — JVM CLI клиент к `/agentik` +# `:agentik-cli` — one-shot CLI клиент к `/agentik` ## Что это -JVM-only **one-shot subcommand CLI** к серверу `:standalone` через -`:client` по HTTP+SSE. Один вызов — одна команда: стримы ответа -`send` идёт в stdout построчно, никакого embedded-REPL. +**One-shot subcommand CLI** (Kotlin Multiplatform) к серверу +`:standalone` через `:client` по HTTP+SSE. Один вызов — одна команда: +стрим ответа `send` идёт в stdout построчно, никакого embedded-REPL. Решает: быстрый способ дёрнуть агента из shell-скрипта или руками, не поднимая отдельную TUI-сессию. +## Платформы + +| Платформа | Артефакт | Размер | Статус | +|---|---|---|---| +| `jvm` (JRE 21) | `*-all.jar` | ~7 МБ | ✓ собирается и работает | +| `linuxX64` | `.kexe` | ~5 МБ | ✓ собирается и работает на этом хосте | +| `macosX64` | `.kexe` | — | собирается на macOS-раннере | +| `macosArm64` | `.kexe` | — | собирается на macOS-arm64-раннере | +| `mingwX64` | `.exe` | ~6 МБ | ✓ собирается (cross-compile с Linux) | +| `linuxArm64` | — | — | **нет** — kotlinx-cli 0.3.6 не публикует klib для linuxArm64 | +| `iOS` | — | — | нет смысла на iOS | + ## Подкоманды ``` @@ -38,20 +50,31 @@ agentik-cli send --server http://localhost:8080/agentik conv-abc "привет" ## Как запустить -### Из готового fatjar - -```bash -java --enable-native-access=ALL-UNNAMED \ - -jar agentik-cli-0.1.0-SNAPSHOT-all.jar --help -``` - -### Через Gradle +### JVM (fatjar) ```bash ./gradlew :agentik-cli:shadowJar -java -jar agentik-cli/build/libs/agentik-cli-0.1.0-SNAPSHOT-all.jar info +java --enable-native-access=ALL-UNNAMED \ + -jar agentik-cli/build/libs/agentik-cli-0.1.0-SNAPSHOT-all.jar --help ``` +### Native linuxX64 + +```bash +./gradlew :agentik-cli:linkReleaseExecutableLinuxX64 +./agentik-cli/build/bin/linuxX64/releaseExecutable/agentik-cli.kexe --help +``` + +### Native macOS / Windows + +На Linux-хосте `macosX64`/`macosArm64` линкуются пустыми (нужен +macOS-раннер, Apple Mach-O формат). `mingwX64` собирается через +кросс-компиляцию. + +CI-ноут: запускать `./gradlew :agentik-cli:linkReleaseExecutableMacosX64 +:agentik-cli:linkReleaseExecutableMacosArm64` на `macos-latest` +раннере Gitea Actions. + ## Примеры ```bash @@ -76,6 +99,9 @@ agentik-cli interrupt --server http://localhost:8080/agentik "$ID" # Удалить agentik-cli conv-delete --server http://localhost:8080/agentik "$ID" + +# Через env-переменную +AGENTIK_SERVER=http://localhost:8080/agentik agentik-cli info ``` ## Формат вывода `send` @@ -94,28 +120,39 @@ event End Терминальные события (`End`, `Interrupted`, `Error`) тоже печатаются; CLI выходит сразу после `End`. -## Почему JVM-only (а не KMP) +## Почему kotlinx.cli (а не clikt) -Аргумент-парсер: `kotlinx.cli` 0.3.6 (JetBrains, KMP) — единственный -зрелый вариант, который линкуется нативно под `linuxX64`/`linuxArm64` -без upstream-багов. Альтернативы: +- **kotlinx.cli 0.3.6** (JetBrains, KMP) — единственный зрелый + arg-parser, который стабильно линкуется под `linux_x64` + + `macos_x64`/`macos_arm64` + `mingw_x64`. Минус: нет `linux_arm64`. +- **clikt-multiplatform 5.x** (ajalt) — имеет linuxArm64, но + ломается на native linker: `duplicate symbol selfAndAncestors` + между `clikt` и `clikt-mordant` commonMain (issue + [ajalt/clikt#598](https://github.com/ajalt/clikt/issues/598)). + Workaround `kotlin.native.cacheKind.linuxX64=none` замедляет + сборку на порядки и не решает проблему до конца. Поэтому clikt + отвергнут. -- **clikt-multiplatform 5.x** имеет upstream-баг: `clikt` и - `clikt-mordant` оба объявляют `selfAndAncestors` в commonMain, и - Kotlin/Native linker падает на дубликате символа - (issue [ajalt/clikt#598](https://github.com/ajalt/clikt/issues/598), - workaround — `kotlin.native.cacheKind.linuxX64=none`, что замедляет - сборку на порядки). Поэтому clikt отвергнут в пользу kotlinx.cli. -- **picocli** — JVM-only. -- **kotlinx-args** — мёртвый репозиторий. +## Платформенные детали -Нативные бинари `:agentik-cli` отложены до стабилизации -arg-parser-ситуации. +- **entryPoint на K/N** — это FQN функции **без** суффикса `Kt` + (т.е. `pw.binom.agentik.cli.main`, а не `MainKt.main`). JVM + convention `MainKt.main` тут не работает — K/N линкер ищет + функцию по `package.main`. +- **`platformEnv(key)`** для чтения env-переменных: + - JVM: `System.getenv(key)` через `jvmMain` actual. + - Native: `getenv(key)` из `platform.posix` через + `kotlinx.cinterop.toKString()` (`nativeMain` actual, + требует `@OptIn(ExperimentalForeignApi::class)`). +- **Stdout / exit code** — работают на K/N через корутины. ## Тесты +Тесты для подкоманд пока не написаны (TODO). Базовый smoke +покрывается руками против живого сервера. + ```bash -./gradlew :agentik-cli:jvmTest +./gradlew :agentik-cli:jvmTest # 0/0 — пока пусто ``` ## Версии diff --git a/agentik-cli/build.gradle.kts b/agentik-cli/build.gradle.kts index e39f252..40cb8a1 100644 --- a/agentik-cli/build.gradle.kts +++ b/agentik-cli/build.gradle.kts @@ -11,10 +11,23 @@ plugins { kotlin { jvmToolchain(21) - // Одноразовый CLI. Пока только JVM. Native-бинари добавим позже — - // kotlinx.cli 0.3.6 не имеет linuxArm64, а clikt имеет upstream-баг - // с duplicate symbol selfAndAncestors между clikt и clikt-mordant. + // Native-таргеты, которые покрывает kotlinx.cli 0.3.6 (см. его .module + // в Maven Central): linux_x64, macos_x64, macos_arm64, mingw_x64. + // linuxArm64 не входит — kotlinx.cli 0.3.6 для него не публикуется + // (последний релиз 2023-09, KMP-targets зафиксированы). clikt-multiplatform + // 5.x имеет linuxArm64, но ломается на duplicate symbol `selfAndAncestors` + // между clikt и clikt-mordant при линковке native (issue ajalt/clikt#598), + // поэтому clikt отвергнут. + // + // iOS не входит: :agentik-cli бессмыслен на iOS, а :client (единственный + // его потребитель) тоже без iOS. jvm() + listOf( + linuxX64(), + macosX64(), + macosArm64(), + mingwX64(), + ) sourceSets { commonMain.dependencies { @@ -29,8 +42,8 @@ kotlin { implementation(libs.kotlinx.coroutines.core) } - // :agentik-cli — JVM-only one-shot CLI. Нет jvmMain / nativeMain. - // Всё (включая platformEnv) лежит в commonMain. + // :agentik-cli — commonMain-only (нет jvmMain/nativeMain разделения): + // весь код, включая platformEnv, лежит в commonMain. } @OptIn(ExperimentalKotlinGradlePluginApi::class) @@ -41,6 +54,21 @@ kotlin { } } } + + // entryPoint на K/N — это FQN функции БЕЗ суффикса `Kt` + // (Java/Kotlin convention `MainKt.main` тут не работает, линкер K/N ищет + // функцию как `package.main`). На JVM суффикс `Kt` сохраняется через + // mainClass.set(...) выше. + listOf( + linuxX64(), + macosX64(), + macosArm64(), + mingwX64(), + ).forEach { + it.binaries.executable { + entryPoint = "pw.binom.agentik.cli.main" + } + } } // Fatjar — аналог :standalone. diff --git a/agentik-cli/src/commonMain/kotlin/pw/binom/agentik/cli/PlatformEnv.kt b/agentik-cli/src/commonMain/kotlin/pw/binom/agentik/cli/PlatformEnv.kt index 57ed8e0..cd13c8a 100644 --- a/agentik-cli/src/commonMain/kotlin/pw/binom/agentik/cli/PlatformEnv.kt +++ b/agentik-cli/src/commonMain/kotlin/pw/binom/agentik/cli/PlatformEnv.kt @@ -1,3 +1,3 @@ package pw.binom.agentik.cli -internal fun platformEnv(key: String): String? = System.getenv(key) +internal expect fun platformEnv(key: String): String? diff --git a/agentik-cli/src/jvmMain/kotlin/pw/binom/agentik/cli/PlatformEnv.kt b/agentik-cli/src/jvmMain/kotlin/pw/binom/agentik/cli/PlatformEnv.kt new file mode 100644 index 0000000..0d9612a --- /dev/null +++ b/agentik-cli/src/jvmMain/kotlin/pw/binom/agentik/cli/PlatformEnv.kt @@ -0,0 +1,3 @@ +package pw.binom.agentik.cli + +internal actual fun platformEnv(key: String): String? = System.getenv(key) diff --git a/agentik-cli/src/nativeMain/kotlin/pw/binom/agentik/cli/PlatformEnv.kt b/agentik-cli/src/nativeMain/kotlin/pw/binom/agentik/cli/PlatformEnv.kt new file mode 100644 index 0000000..1e5023d --- /dev/null +++ b/agentik-cli/src/nativeMain/kotlin/pw/binom/agentik/cli/PlatformEnv.kt @@ -0,0 +1,8 @@ +package pw.binom.agentik.cli + +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.toKString +import platform.posix.getenv + +@OptIn(ExperimentalForeignApi::class) +internal actual fun platformEnv(key: String): String? = getenv(key)?.toKString()