Binary, CLI and Desktop App Naming

binaries-and-cli specs/binaries-and-cli/naming.kmd

Nomenclatura canônica para executáveis: binário k<slug>, dir /opt/koder/<slug>/, D-Bus ID dev.koder.<slug>, symlink em /usr/local/bin/, .desktop file, aliases de compatibilidade. Cobre Flutter desktop, CLIs, multi-binary tooling, packaging .deb/.rpm/.kpkg. Android applicationId §11: dev.koder.<short_slug>, source-of-truth em koder.toml [android] application_id, exposto via catálogo da Store — clients NUNCA derivam por heurística.

When this spec applies

Primary triggers

All triggers

Specification body

Spec: Binary, CLI and Desktop App Naming

Apply this spec when:

  • Creating or renaming a Flutter desktop app binary
  • Creating or renaming a Koder CLI tool (single binary, no GUI)
  • Building a multi-binary tooling family (e.g., dev/koder-tools)
  • Packaging a product as a .deb, .rpm, .AppImage, or .kpkg
  • Setting the D-Bus application ID for a Linux desktop app
  • Creating a .desktop file or systemd service unit for a Koder product
  • Deciding the CLI command name a user types to launch a product

1. Core rule: slug = binary = install argument

Every Koder product or CLI has a slug that is simultaneously:

slug  =  binary name  =  kpkg install argument
  ↑ all three are the same string — no transformations

The slug is chosen at product creation and never transformed at packaging or install time. Installers (khub, kpkg) use the slug verbatim as the binary name and install destination — no prefix is prepended, no suffix stripped.

So for kterm: slug kterm, binary kterm, install kpkg install kterm. For kdedup: slug kdedup, binary kdedup, install kpkg install kdedup. No exceptions, no transformations.

Slug naming convention

Choose slugs that start with k followed by a short lowercase noun: kterm, khub, kmail, kjet, kdek. Multi-binary families use koder-<verb> (§10). These conventions are a guide for new products, not a transformation applied after the fact.

1.0 Display name parity rule

The slug is also the source of truth for the display name — the human-readable label that appears on the launcher tile, the OS window title, the Settings menu entry, the README H1, and the Hub package listing. Mechanical rule:

DisplayName = TitleCase(slug)

Worked examples:

Slug DisplayName
kruze Kruze
kterm KTerm
kmail KMail
kdek KDek
dek Dek
mosaic Mosaic
hub Hub
kode Kode
koder-tools Koder Tools

The "Koder " prefix that used to live in launcher tiles ("Koder Term", "Koder Mail") moves to Comment= of the .desktop file and to marketing/long-form copy. The launcher tile shows the short DisplayName; GNOME / KDE search still match "koder" via Comment + Keywords (§5).

Rationale:

  • One source of truth — rename the folder, rename the slug, the brand follows automatically.
  • Removes the brand-family redundancy when the user already sees a cluster of K-prefixed icons in their launcher.
  • New products only choose the slug; everything else derives.

Override (rare)

A product whose marketing brand is intentionally not TitleCase(slug) (B2B partner brand, white-label, legal-mandated naming) sets the override in its koder.toml:

[package]
slug = "raven"
display_name = "Raven Mail"   # default would be "Raven"

kicon validate-metadata reads this field and uses it for Name= in the generated .desktop file. The override must be documented in the product's README (one-liner explaining why it diverges) and is the only place the override may live — never inline-edited in build scripts or .desktop files.

1.1 Flutter desktop apps (full GUI products)

These follow §2–§7 in full: D-Bus ID, .desktop file, /opt/koder/<slug>/ install layout, optional koder-<slug>.service for background daemons.

Product Monorepo path Slug Binary / CLI command
Koder Hub products/dev/hub/ khub khub
Koder Mail products/horizontal/kmail/ kmail kmail
Koder Jet infra/jet/ kjet kjet
Koder Drive products/horizontal/drive/ kdrive kdrive
Koder Dek products/horizontal/dek/ kdek kdek
Koder Term products/dev/kterm/ kterm kterm

1.2 Pure CLI tools (no GUI)

These follow §9 — single binary in /usr/local/bin/<slug>, no D-Bus, no .desktop file, no /opt/koder/<slug>/ bundle.

CLI Monorepo path Slug Binary Install
Koder Icon products/dev/kicon/ kicon kicon kpkg install kicon
Koder Dedup dev/kdedup/ kdedup kdedup kpkg install kdedup
Koder Shell linux/shell/ kosh kosh kpkg install kosh

1.3 Multi-binary tooling families

Umbrella modules that ship several related binaries together follow §10 — prefix koder-<verb> for each binary, single kpkg install for the family.

Family Monorepo path Binaries Install
Koder Tools dev/koder-tools/ koder-lock, koder-stackdoc, koder-pathspec-commit, koder-backlog-grep, koder-hub-check kpkg install koder-tools

2. Flutter Linux — CMakeLists.txt

# linux/CMakeLists.txt
set(BINARY_NAME "khub")   # ← equals the slug, not the Dart package name

This makes the build output build/linux/x64/release/bundle/khub.

The Dart package name in pubspec.yaml (name: koder_hub) is irrelevant to the binary name; Flutter uses BINARY_NAME for the native executable only.


3. Installation layout

/opt/koder/<slug>/          ← product bundle root
    <slug>                  ← main executable (name equals slug)
    lib/                    ← shared libraries (.so)
    data/                   ← Flutter assets, fonts, ICU
/usr/local/bin/<slug>       ← symlink → /opt/koder/<slug>/<slug>

The symlink in /usr/local/bin/ is the user-facing CLI command.

Legacy cleanup: if a previous version installed under /opt/koder-<slug>/, the .deb prerm script must remove that directory on upgrade:

# DEBIAN/prerm
#!/bin/sh
rm -rf /opt/koder-<slug> 2>/dev/null || true

4. D-Bus application ID

dev.koder.<slug>

Examples: dev.koder.khub, dev.koder.kmail, dev.koder.kjet, dev.koder.kterm

Set in the GTK application registration inside linux/my_application.cc:

gtk_application_new("dev.koder.hub", G_APPLICATION_DEFAULT_FLAGS)

This value also becomes the GLib application ID and the desktop file name (see §5). It must match exactly across all three places.


5. Desktop file

Filename: dev.koder.<slug>.desktop Install path: /usr/share/applications/dev.koder.<slug>.desktop

Minimum required fields:

[Desktop Entry]
Name=KHub
GenericName=App Hub
Comment=Koder Hub — universal app store for the Koder ecosystem
Keywords=koder;hub;apps;store;packages;
Exec=/opt/koder/khub/khub %u
Icon=dev.koder.khub
Terminal=false
Type=Application
Categories=Utility;Network;
MimeType=x-scheme-handler/koderhub;
StartupWMClass=khub
  • Name is TitleCase(slug) per §1.0 (display-name parity rule). The override in koder.toml [package].display_name wins when present.
  • GenericName is the function in 2–3 words ("App Hub", "Web Browser", "Audio Recorder"). Optional but recommended for KDE/GNOME accessibility.
  • Comment carries the long-form "Koder <DisplayName> — <one-liner>" so launcher search keeps matching "koder" without showing redundant text on the tile.
  • Keywords always includes koder;<slug>; followed by 2–4 functional terms, semicolon-terminated.
  • Icon uses the reverse-domain ID so icon themes can scope icons per product.
  • StartupWMClass must match the binary name so the taskbar groups windows correctly.
  • %u passes a URL argument (for deep-link / URI-scheme handling).

Icon install path:

/usr/share/icons/hicolor/512x512/apps/dev.koder.<slug>.png

6. Systemd user service (daemons and background agents)

Products that run background agents (e.g., update checker, sync daemon) use:

koder-<slug>.service          ← unit file name
koder-<slug>                  ← ExecStart binary

The koder- prefix disambiguates Koder services from OS system services and third-party packages that may also use short names.


7. Compatibility aliases (one-version grace period)

When renaming an existing binary (e.g., koder-hubkhub), the .deb must ship a compatibility symlink for exactly one release cycle:

# DEBIAN/postinst — create backward-compat alias
ln -sf /usr/local/bin/khub /usr/local/bin/koder-hub

Remove the alias in the next release's prerm:

# DEBIAN/prerm — remove old alias
rm -f /usr/local/bin/koder-hub

8. Flutter desktop apps — quick checklist

When packaging a new or renamed Flutter desktop product (covers §2–§7):

  • linux/CMakeLists.txtBINARY_NAME "<slug>"
  • linux/CMakeLists.txtAPPLICATION_ID "dev.koder.<slug>"
  • Install dir: /opt/koder/<slug>/
  • Symlink: /usr/local/bin/<slug>/opt/koder/<slug>/<slug>
  • Desktop file named dev.koder.<slug>.desktop
  • Icon at /usr/share/icons/hicolor/512x512/apps/dev.koder.<slug>.png
  • StartupWMClass=<slug> in desktop file
  • kpkg.toml with [platforms], [install], and [install.desktop] (including startup_wm_class and categories)
  • Compatibility alias if renaming (/usr/local/bin/<old-name> for one release)
  • prerm cleans /opt/koder-<slug>/ if upgrading from legacy layout

8.1 Installer responsibility

The installers (khub and kpkg) are responsible for creating all desktop integration artifacts.desktop file, icon, and /usr/local/bin/ symlink — on behalf of the package.

The source of truth for all installation metadata is the kpkg.toml embedded in the bundle, not the Koder Hub API. This means:

  • kpkg install <file.kpkg> reads kpkg.toml from the ZIP, resolves install paths, creates the .desktop file, and installs the icon — no API call needed.
  • khub install <slug> downloads the .kpkg from the Store, then delegates to the same installer.Install() library path — identical behavior.
  • Metadata like StartupWMClass and XDG Categories come from [install.desktop] in kpkg.toml, ensuring they are consistent regardless of which tool performs the install.

Apps must not bundle their own installer scripts for desktop integration; the kpkg installer library handles it for all platforms.


9. Pure CLI tools (no GUI)

A pure CLI tool is a single-binary Koder utility with no GUI, no D-Bus service registration, no .desktop file, and no /opt/koder/<slug>/ install bundle. Examples: kicon, kdedup, kosh. Sections §2 (Flutter CMakeLists), §4 (D-Bus ID), §5 (desktop file), and §7 (compatibility aliases) do not apply to CLI tools.

9.1 Identity

The §1 identity rule: slug = binary name = kpkg install argument — all the same string, no transformation.

dev/kdedup/        ← module directory
  ↓
slug = "kdedup"    ← in koder.toml
  ↓
binary = "kdedup"  ← in koder.toml + Go's `go build -o`
  ↓
kpkg install kdedup  ← user-facing install command

9.2 koder.toml

A pure CLI's koder.toml declares slug and binary as identical strings:

[app]
slug    = "kdedup"
binary  = "kdedup"
version = "0.1.0"

Both fields must be present and identical. koder-stackdoc check flags divergence as KSTORE-TOML-001.

9.3 Installation layout

A single executable in /usr/local/bin/. No /opt/koder/<slug>/ bundle, no lib/, no data/ — Go-built CLI binaries are statically linked.

/usr/local/bin/<slug>      ← the binary itself (not a symlink)

If the CLI ships any auxiliary data (config defaults, completion scripts), it goes under XDG paths:

/usr/share/<slug>/          ← read-only data (templates, completions)
~/.config/<slug>/           ← user config
~/.cache/<slug>/            ← user cache

Never under /opt/.

9.4 Distribution

Three channels, in order of preference:

  1. kpkg install <slug> — universal Koder Hub distribution (preferred once the module ships a kpkg.toml)
  2. Pre-built binarycp dev/<slug>/<slug> /usr/local/bin/ for local builds during development
  3. Build from sourcecd dev/<slug> && GOWORK=off go build -o <slug> ./cmd/<slug> for contributors

The koder.toml and the README must show all three; the install command in kpkg install is always the slug verbatim — never an alias, never a shorter form.

9.5 Background daemons

If a CLI tool also exposes a long-running daemon mode (e.g., kicon watch), the systemd user service follows §6 (koder-<slug>.service with ExecStart=/usr/local/bin/<slug> daemon).

9.6 Quick checklist

When creating or renaming a pure CLI tool:

  • Module directory = dev/<slug>/ (or appropriate Area), where <slug> starts with k if appropriate
  • koder.toml has slug and binary set to the same string
  • Go build produces single binary named <slug> (go build -o <slug> ./cmd/<slug>)
  • Binary lands at /usr/local/bin/<slug> (no /opt/, no symlink chain)
  • README install section shows all three channels (kpkg, pre-built copy, build from source) with the slug verbatim
  • No .desktop file, no D-Bus ID, no gtk_application_new
  • If the CLI has a daemon mode: §6 systemd unit named koder-<slug>.service

10. Multi-binary tooling families

A tooling family is an umbrella module that ships several related binaries together — they share a release cadence, a CHANGELOG, and a single kpkg install invocation. Example: dev/koder-tools ships koder-lock, koder-stackdoc, koder-pathspec-commit, koder-backlog-grep, koder-hub-check as one bundle.

Use a tooling family when:

  • The binaries share a domain (e.g., monorepo housekeeping helpers)
  • They are versioned and released together
  • Splitting them into separate dev/<slug>/ modules would create five near-empty repos with synchronized release cycles

Otherwise, prefer §9 (one CLI = one module).

10.1 Identity

The umbrella module is named koder-tools (or koder-<domain> for other families). Each binary inside is named koder-<verb> — a verb describing what the tool does, prefixed with koder- to disambiguate from system tools.

dev/koder-tools/                          ← umbrella module directory
  cmd/koder-lock/main.go                  → binary: koder-lock
  cmd/koder-stackdoc/main.go              → binary: koder-stackdoc
  cmd/koder-pathspec-commit/main.go       → binary: koder-pathspec-commit
  cmd/koder-backlog-grep/main.go          → binary: koder-backlog-grep
  cmd/koder-hub-check/main.go           → binary: koder-hub-check

The umbrella never exposes a binary named just koder-tools — the family is a delivery vehicle, not a launcher. Each binary stands on its own.

10.2 koder.toml

The umbrella koder.toml declares the family slug and lists all binaries shipped:

[app]
slug    = "koder-tools"
version = "0.2.0"

[[binaries]]
name = "koder-lock"
path = "cmd/koder-lock"

[[binaries]]
name = "koder-stackdoc"
path = "cmd/koder-stackdoc"
# ... one [[binaries]] block per binary

binary = "..." (singular) is omitted at the top level — the family has no single primary binary.

10.3 Installation layout

Same as §9: each binary lands in /usr/local/bin/<binary-name>, all installed by a single kpkg install call:

/usr/local/bin/koder-lock
/usr/local/bin/koder-stackdoc
/usr/local/bin/koder-pathspec-commit
/usr/local/bin/koder-backlog-grep
/usr/local/bin/koder-hub-check

10.4 Distribution

kpkg install koder-tools     # installs all binaries in the family

Granular install (kpkg install koder-lock for just one binary) is not supported — families ship as a unit. If a binary needs an independent release cadence, promote it to its own §9 module.

10.5 Why koder-<verb> and not k<verb>

Tooling-family binaries deliberately use the longer koder- prefix instead of the short k. Two reasons:

  1. Disambiguation from products. klock could plausibly be a future Koder product (a password manager?); koder-lock clearly signals "internal Koder Stack tooling, not an end-user app."
  2. Verb-shaped names. Tooling-family binaries are verbs (lock, stackdoc, commit, grep, check); product binaries are nouns (store, mail, dek). The koder-<verb> form reads naturally as "koder, do X."

End-user-facing CLI tools (kicon, kdedup, kosh) use §9's short slug form (kicon, kdedup, kosh) because they are user-facing nouns/products in their own right.

10.6 Quick checklist

When creating or extending a tooling family:

  • Umbrella directory named dev/koder-<domain>/ (e.g., dev/koder-tools/)
  • Each binary under cmd/koder-<verb>/main.go
  • Each binary named koder-<verb> — no short k<verb> aliases
  • Top-level koder.toml lists all binaries under [[binaries]]
  • No top-level binary = "..." field
  • Single CHANGELOG covers all binaries; one version applies to the family
  • kpkg install koder-<domain> installs all binaries at once

11. Android applicationId

For Koder products with Android apps (Flutter or native Kotlin/Compose), the Android applicationId follows a canonical rule rooted in the same slug used elsewhere in this spec.

11.1 Rule

applicationId  =  dev.koder.<short_slug>

where short_slug is the catalog slug with the koder- prefix stripped (when present) and any - replaced by _:

Catalog slug short_slug applicationId
koder-eye eye dev.koder.eye
koder-kruze kruze dev.koder.kruze
koder-pass pass dev.koder.pass
koder-dek dek dev.koder.dek
kmail kmail dev.koder.kmail
khub khub dev.koder.khub
kterm kterm dev.koder.kterm
kode kode dev.koder.kode
multi-word multi_word dev.koder.multi_word

For slugs that already start with k (the §1 short form), no stripping happens — they're used as-is. The resulting applicationId is also the package name in the manifest (<manifest package="…"> and the namespace in build.gradle.kts).

11.2 Source of truth

The canonical applicationId for each product must be declared in koder.toml, not derived by heuristic. This is the ground truth that the Store catalog, build pipeline, and clients all consume:

# koder.toml
[app]
slug = "koder-eye"

[android]
application_id = "dev.koder.eye"

When [android].application_id is absent, the build tooling may fall back to the §11.1 formula — but only at build time, never at runtime in clients. Production catalog rows MUST have the explicit field populated; clients MUST consume it from the catalog API.

11.3 Catalog API contract

The Koder Hub catalog exposes package_name for every Android- distributing app:

GET https://hub.koder.dev/api/v1/apps/koder-eye
{
  "slug": "koder-eye",
  "name": "Koder Eye",
  "version": "0.2.2",
  "platforms": ["android"],
  "package_name": "dev.koder.eye"
}

Clients (Koder Hub mobile, third-party update checkers, IDE integrations) must read package_name from the catalog. The Store mobile app's _expectedPkg heuristic in app_detail_screen.dart is an anti-pattern and is being removed in favor of the catalog field (see commit history of products/dev/hub/app/lib/screens/app_detail_screen.dart).

If the API response omits package_name, the client treats the entry as not installable until the catalog row is backfilled — never fall back to a derived guess. A stale install record is preferable to a destructive operation against the wrong OS package.

11.4 Build-time wiring

In a Flutter Android app:

// android/app/build.gradle.kts
android {
    namespace = "dev.koder.eye"   // §11.1 applicationId

    defaultConfig {
        applicationId = "dev.koder.eye"
    }
}

In a native Android (Kotlin/Compose) app, the same fields apply. The AndroidManifest.xml package attribute is implied by namespace for AGP ≥ 8 and need not be set explicitly.

The Kotlin top-level package directories under app/src/main/kotlin/ mirror the applicationId, e.g., app/src/main/kotlin/dev/koder/eye/MainActivity.kt.

11.5 Legacy exceptions (transition state, 2026-04-28)

Four products in production still use non-canonical applicationIds that predate this spec:

Product Slug Current applicationId Canonical (§11.1) Migration
Koder Hub koder-hub dev.koder.koder_store dev.koder.khub TODO
Koder Mosaic koder-mosaic dev.koder.koder_mosaic dev.koder.kmosaic TODO
Koder Term kterm dev.koder.ticsign dev.koder.kterm TODO
Koder Mail koder-mail dev.koder.kmail dev.koder.mail (or canonicalize slug to kmail) TODO

Each migration requires:

  1. Update koder.toml [android].application_id
  2. Update build.gradle.kts namespace and applicationId
  3. Move Kotlin source under app/src/main/kotlin/dev/koder/<new>/
  4. Backfill the catalog DB row's package_name
  5. Bump version + ship release with both the old and new applicationId for one cycle (Android does not allow renaming an applicationId of an installed app — users must reinstall, so the old package is archived in the catalog and the new one becomes the active product)
  6. Open follow-up ticket per product

Until each migration ships, the catalog row's package_name reflects the current value (e.g., dev.koder.koder_store), so clients continue to work against installed users.

11.6 Quick checklist

When creating a new Android Koder app or auditing an existing one:

  • koder.toml has [android].application_id declared and matches §11.1
  • android/app/build.gradle.kts namespace and applicationId match
  • Kotlin/Java source rooted at app/src/main/<lang>/dev/koder/<short_slug>/
  • Catalog API row has package_name populated and matches koder.toml
  • Client consumers read package_name from API — no client-side heuristic on slug → packageName
  • If product is on the §11.5 legacy list, follow-up ticket exists with concrete migration steps
  • App icon resource path: android/app/src/main/res/mipmap-*/ (drawables are per Android resource convention; the spec at specs/icons/generation-targets.kmd covers icon generation)

References