65e05612a1
- conv-ls/new/show/delete/rename -> вложенные под agentik-cli conv
- ConvCommand — Subcommand-родитель, регистрирует 5 дочерних
команд в init { subcommands(...) }
- ConvSubcommand(name, description) extends AgentikSubcommand —
базовый класс для всех conv-подкоманд (наследует --server/--id)
Два гоччаса kotlinx.cli 0.3.6 которые пришлось обойти:
1. parent.execute() вызывается ПОСЛЕ leaf.execute() всегда когда
leaf достигнут через parent. Если parent делает что-то в
execute() — вывод дублируется после каждой дочерней команды.
Фикс: ConvCommand.execute() = Unit (no-op). Дочерние команды
смотрятся через 'agentik-cli conv --help'.
2. По умолчанию 'conv new --server ...' парсится как
conv[--server ...] + позиционный arg 'new' на уровне
родителя, и дочерняя команда не запускается. Фикс:
ArgParser(strictSubcommandOptionsOrder = true) — все аргументы
после имени subcommand передаются в его парсер.
Smoke (linuxX64 kexe + JVM fatjar): conv ls/new/rename/show/delete
+ msgs/send/interrupt/info работают.
178 lines
7.4 KiB
Markdown
178 lines
7.4 KiB
Markdown
# `:agentik-cli` — one-shot CLI клиент к `/agentik`
|
||
|
||
## Что это
|
||
|
||
**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 |
|
||
|
||
## Подкоманды
|
||
|
||
```
|
||
agentik-cli <command> [args...]
|
||
|
||
Команды верхнего уровня:
|
||
conv <subcommand> операции над диалогами (см. ниже)
|
||
msgs <id> [--limit N] показать сообщения
|
||
send <id> <text...> отправить ход, стримит response-события в stdout
|
||
interrupt <id> прервать текущий ход
|
||
info показать конфиг (server URL + agent id)
|
||
|
||
Подкоманды `conv`:
|
||
conv ls список диалогов
|
||
conv new [--temp] создать диалог, печатает id
|
||
conv show <id> метаданные диалога
|
||
conv delete <id> удалить диалог
|
||
conv rename <id> <title> переименовать
|
||
```
|
||
|
||
`--server URL` и `--id ID` (env: `AGENTIK_SERVER`, `AGENTIK_AGENT_ID`)
|
||
задаются **после** имени subcommand'а — kotlinx.cli не шарит опции
|
||
родителя в subcommand. Примеры:
|
||
|
||
```bash
|
||
agentik-cli conv ls --server http://192.168.76.166:8080/agentik
|
||
agentik-cli conv new --server http://localhost:8080/agentik
|
||
agentik-cli send --server http://localhost:8080/agentik conv-abc "привет"
|
||
agentik-cli info # через AGENTIK_SERVER env-переменную
|
||
```
|
||
|
||
## Как запустить
|
||
|
||
### JVM (fatjar)
|
||
|
||
```bash
|
||
./gradlew :agentik-cli:shadowJar
|
||
java --enable-native-access=ALL-UNNAMED \
|
||
-jar agentik-cli/build/libs/agentik-cli-0.1.0-SNAPSHOT-all.jar conv --help
|
||
```
|
||
|
||
### Native linuxX64
|
||
|
||
```bash
|
||
./gradlew :agentik-cli:linkReleaseExecutableLinuxX64
|
||
./agentik-cli/build/bin/linuxX64/releaseExecutable/agentik-cli.kexe conv --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
|
||
# Список диалогов (таблица)
|
||
agentik-cli conv ls --server http://localhost:8080/agentik
|
||
|
||
# Создать диалог
|
||
ID=$(agentik-cli conv new --server http://localhost:8080/agentik)
|
||
echo "new conv: $ID"
|
||
|
||
# Переименовать
|
||
agentik-cli conv rename --server http://localhost:8080/agentik "$ID" "мой чат"
|
||
|
||
# Отправить ход и стримить ответ
|
||
agentik-cli send --server http://localhost:8080/agentik "$ID" "2+2"
|
||
|
||
# Показать последние N сообщений
|
||
agentik-cli msgs --server http://localhost:8080/agentik "$ID" --limit 10
|
||
|
||
# Прервать активный ход
|
||
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`
|
||
|
||
Каждое SSE-событие печатается отдельной строкой `event <Type> ...` —
|
||
пригодно для парсинга через `awk`/`jq`-обёртки:
|
||
|
||
```
|
||
event StartReasoning
|
||
event StartResponse TEXT
|
||
event AppendText \n\n
|
||
event AppendText Привет!
|
||
event End
|
||
```
|
||
|
||
Терминальные события (`End`, `Interrupted`, `Error`) тоже
|
||
печатаются; CLI выходит сразу после `End`.
|
||
|
||
## Почему kotlinx.cli (а не clikt)
|
||
|
||
- **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
|
||
отвергнут.
|
||
|
||
## Платформенные детали
|
||
|
||
- **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 через корутины.
|
||
|
||
## Готчасы kotlinx.cli
|
||
|
||
- **Вложенные subcommands + parent.execute().** В kotlinx.cli 0.3.6
|
||
`parent.execute()` вызывается ПОСЛЕ `leaf.execute()` всегда,
|
||
когда leaf был достигнут через parent. Поэтому `ConvCommand.execute()`
|
||
сделан no-op (`override fun execute() = Unit`), иначе вывод
|
||
дочерней команды дублируется выводом родителя. Дочерние команды
|
||
смотрятся через `agentik-cli conv --help`.
|
||
- **strictSubcommandOptionsOrder.** Без этого флага `conv new --server ...`
|
||
парсится как `conv [--server ...]` + позиционный аргумент `new`
|
||
на уровне родителя — и дочерняя команда не запускается.
|
||
В `ArgParser` сразу включается `strictSubcommandOptionsOrder = true`.
|
||
|
||
## Тесты
|
||
|
||
Тесты для подкоманд пока не написаны (TODO). Базовый smoke
|
||
покрывается руками против живого сервера.
|
||
|
||
```bash
|
||
./gradlew :agentik-cli:jvmTest # 0/0 — пока пусто
|
||
```
|
||
|
||
## Версии
|
||
|
||
`gradle/libs.versions.toml` → `[versions] agentik-agentik-cli`.
|