Files
agentik/agentik-cli
subochev c9995b263e refactor(protocol): add toolName to ToolResult, remove proto typealiases, rename id→toolCallId, drop Conversation.events()
Three protocol-level changes from Android-client review (items 1-3, 5-6):

1) toolName denormalization in ToolResult (3 layers):
   - :outbox-api/Event.ToolResult: +toolName: String? = null
   - :journal-api/MessageRecord.ToolResult: +toolName: String? = null
   - :proto/Message.ToolResult: +toolName: String? = null
   - :storage-ksqlite, :journal-ksqlite ResultPayload codec: +toolName
   - :standalone/ToolDispatcher, ConversationLoop: thread toolName = call.name
   Nullable + default = backward-compat for already-persisted histories
   and existing clients.

2) Drop proto/Event.kt, AgentEvent.kt, CommonEvent.kt typealiases.
   is proto.Event.End failed with 'Unresolved reference End' (alias
   loses nested-class access). Use pw.binom.agentik.outbox.{Event,
   AgentEvent, CommonEvent} directly everywhere — :proto already has
   api(:outbox-api), the package is visible to consumers, no shim
   needed. 21 files rewired, 3 files deleted.

3) Rename Event.ToolResult.id → toolCallId (option B per user).
   In :outbox-api Event.ToolResult.id == Event.ToolCall.id (one value,
   one name); the persistent journal keeps MessageRecord.ToolResult.id
   as its own PK + toolCallId as FK to the call — different semantics,
   left untouched. Fixed ToolDispatcher bug: emitted id = resultId
   while KDoc claimed id == ToolCall.id; now emits toolCallId = callId.

4) Remove Conversation.events() from :proto; OutboxStore is sole event source.
   Conversation is a pure per-conversation abstraction (send/getMessages/
   rename/close). Live events only via agent.outbox.conversationEvents/
   agentEvents/events. HTTP route /conversations/{id}/events stays for
   wire-compat but routes through outbox internally (map { it.event }).

jvmTest green (95 tasks).
2026-09-22 02:55:02 +03:00
..

: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. Примеры:

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)

./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

./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.

Примеры

# Список диалогов (таблица)
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). 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 покрывается руками против живого сервера.

./gradlew :agentik-cli:jvmTest   # 0/0 — пока пусто

Версии

gradle/libs.versions.toml → [versions] agentik-agentik-cli.