{"id":51010701,"url":"https://github.com/getarcaneapp/libarcane-kotlin","last_synced_at":"2026-06-21T02:01:39.881Z","repository":{"id":361986817,"uuid":"1255662055","full_name":"getarcaneapp/libarcane-kotlin","owner":"getarcaneapp","description":"Kotlin API Client for Arcane","archived":false,"fork":false,"pushed_at":"2026-06-02T04:12:10.000Z","size":190,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-02T06:11:28.562Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/getarcaneapp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-01T04:02:16.000Z","updated_at":"2026-06-02T04:12:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/getarcaneapp/libarcane-kotlin","commit_stats":null,"previous_names":["getarcaneapp/libarcane-kotlin"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/getarcaneapp/libarcane-kotlin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getarcaneapp%2Flibarcane-kotlin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getarcaneapp%2Flibarcane-kotlin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getarcaneapp%2Flibarcane-kotlin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getarcaneapp%2Flibarcane-kotlin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/getarcaneapp","download_url":"https://codeload.github.com/getarcaneapp/libarcane-kotlin/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/getarcaneapp%2Flibarcane-kotlin/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34591166,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-21T02:00:05.568Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-21T02:01:35.605Z","updated_at":"2026-06-21T02:01:39.877Z","avatar_url":"https://github.com/getarcaneapp.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Arcane Kotlin\n\nHand-written Kotlin SDK for the [Arcane](https://github.com/getarcaneapp/arcane) API, for Android (and any JVM) apps that talk to an Arcane manager or agent.\n\n## Overview\n\n`libarcane-kotlin` is a single-layer, idiomatic Kotlin client built on [Ktor](https://ktor.io) and [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization). There is no code generation: every DTO and every endpoint method is hand-crafted to match the Arcane API's types and HTTP surface.\n\nTwo Gradle modules:\n\n- **`arcane-core`** — pure Kotlin/JVM. Auth, token storage interface, environment scoping, REST helpers, WebSocket + NDJSON streams (as `Flow`s), and per-resource services. Runs on any JVM and is unit-tested with Ktor's `MockEngine` (no device/emulator).\n- **`arcane-android`** — thin Android layer: a Keystore-backed secure `TokenStore` and the OIDC browser flow (Custom Tabs). Apps using only API-key or username/password auth, or providing their own token storage, can depend on `arcane-core` alone.\n\nConcurrency is coroutines-first: blocking calls are `suspend` functions and streams are `Flow`s.\n\n## Modules\n\n```kotlin\n// settings.gradle.kts of a consuming project (once published):\ndependencies {\n    implementation(\"app.getarcane:arcane-core:\u003cversion\u003e\")   // JVM/Android core\n    implementation(\"app.getarcane:arcane-android:\u003cversion\u003e\") // Android secure storage + OIDC (optional)\n}\n```\n\n`arcane-core`: Kotlin/JVM (JVM 17 bytecode). `arcane-android`: `com.android.library`, `minSdk 24`.\n\n## Quickstart\n\n```kotlin\nimport app.getarcane.sdk.ArcaneClient\nimport app.getarcane.sdk.ArcaneConfiguration\nimport app.getarcane.sdk.EnvironmentId\nimport app.getarcane.sdk.errors.ArcaneError\n\nval client = ArcaneClient(\n    ArcaneConfiguration(\n        baseUrl = \"https://arcane.example.com\",\n        // On Android, use AndroidSecureTokenStore(context) from arcane-android.\n        defaultEnvironmentId = EnvironmentId(\"0\"),\n    ),\n)\n\n// The client owns an HttpClient + coroutine scope — close it when done (or use `client.use { }`).\ntry {\n    client.auth.login(username = \"admin\", password = \"password\")\n\n    val containers = client.containers.list(envId = EnvironmentId(\"0\"))\n    val first = containers.data.first()\n    client.containers.start(envId = EnvironmentId(\"0\"), id = first.id)\n\n    // Stream logs as a Flow.\n    client.containers.logs(envId = EnvironmentId(\"0\"), id = first.id, follow = true)\n        .collect { line -\u003e println(line.text) }\n} catch (e: ArcaneError.Unauthorized) {\n    // ...\n} catch (e: ArcaneError.Validation) {\n    e.fields.forEach { (field, messages) -\u003e println(\"$field: $messages\") }\n} finally {\n    client.close()\n}\n```\n\n### Authentication\n\nThree paths:\n\n- **API key** — set `apiKey` on `ArcaneConfiguration`; sent as `X-API-Key` (takes precedence over a bearer token).\n- **Username / password** — `client.auth.login(username, password)`. Tokens are cached and persisted via the configured `TokenStore`; a 401 triggers a single `auth/refresh` (concurrent calls are de-duplicated) and one retry.\n- **OIDC** — on Android, `OidcAuthenticator(client)` drives the Custom Tabs flow (`startSignIn` → app redirect → `completeSignIn`), or the device-code flow (`beginDeviceFlow` / `pollDeviceToken`).\n\n### Secure token storage (Android)\n\n```kotlin\nimport app.getarcane.sdk.android.AndroidSecureTokenStore\n\nval client = ArcaneClient(\n    ArcaneConfiguration(\n        baseUrl = \"https://arcane.example.com\",\n        tokenStore = AndroidSecureTokenStore(context), // AES-256-GCM via AndroidKeyStore + DataStore\n    ),\n)\n```\n\n`InMemoryTokenStore` (in `arcane-core`) is the default and is used in tests.\n\n### Streaming\n\n- `client.containers.logs(...)` / `swarm.serviceLogs(...)` / `projects.logs(...)` → `Flow\u003cLogLine\u003e` (WebSocket)\n- `client.containers.stats(...)` / `system.statsStream(...)` → `Flow\u003c...\u003e` (WebSocket)\n- `client.containers.exec(...)` → `TerminalSession` (bidirectional: `send(text)` + `output: Flow\u003cByteArray\u003e`)\n- `client.images.pullStream(...)` / `projects.deployStream(...)` → `Flow\u003c...\u003e` (NDJSON progress)\n\nCollecting a stream opens the connection; cancelling the collector closes it.\n\n### Errors\n\nAll failures surface as the sealed `ArcaneError`: `Unauthorized`, `Forbidden`, `NotFound`, `Conflict`, `Validation(fields)`, `RateLimited(retryAfter)`, `Server(code, message)`, `Transport`, `Decoding`, `Unknown`.\n\n### Server capabilities (v1 / v2)\n\nAfter the first authenticated `User` is decoded, `client.serverCapabilities()` reports whether the server speaks v1 legacy roles or v2 RBAC, so apps can gate role-management UI.\n\n## Services\n\nEach resource is exposed as a service on `ArcaneClient`:\n\n| Service | Endpoints |\n| --- | --- |\n| `client.auth` | login, logout, refresh, me, password change, OIDC flow |\n| `client.users` | user CRUD, role assignments |\n| `client.apiKeys` | API key CRUD |\n| `client.roles` / `client.oidcRoleMappings` | v2 RBAC roles + OIDC mappings |\n| `client.environments` | environment CRUD, agent pairing, mTLS bundle |\n| `client.containers` | list, inspect, lifecycle, logs, stats, exec |\n| `client.images` | list, inspect, pull, build, prune, upload |\n| `client.volumes` | volumes, browse, backups |\n| `client.networks` | list, inspect, create, prune, topology |\n| `client.projects` | compose projects: up/down/restart/redeploy/build/pull/destroy/archive |\n| `client.swarm` | swarm: nodes, services, stacks, configs, secrets, tasks |\n| `client.system` | docker info, prune, convert, upgrade, bulk actions |\n| `client.dashboard` | env overview, action items |\n| `client.events` | audit events |\n| `client.webhooks` / `client.notifications` | webhook + notification config |\n| `client.templates` / `client.registries` | templates + container registries |\n| `client.gitops` / `client.builds` / `client.jobs` | GitOps, build workspaces, scheduled jobs |\n| `client.settings` / `client.updater` / `client.vulnerabilities` / `client.ports` / `client.version` | misc |\n\n## Building\n\n```sh\n./gradlew :arcane-core:test          # JVM unit tests (no device needed)\n./gradlew :arcane-android:assembleRelease\nARCANE_TEST_URL=https://your-arcane ./gradlew :arcane-core:test   # also runs the live /health integration test\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetarcaneapp%2Flibarcane-kotlin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgetarcaneapp%2Flibarcane-kotlin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgetarcaneapp%2Flibarcane-kotlin/lists"}