Files
LiJian bc08dbe17d ♻️ refactor: make API key prefix and CLI product naming distribution slots (#18637)
* ♻️ refactor(cli): collect every product-naming string into one module

The CLI's identity was spread across a dozen files: bin names in
`utils/completion.ts`, the config directory duplicated in five places, the
systemd unit name defined twice, the product name in a handful of command
descriptions, and — least visibly — inside `buildNotifyProtocol`, a prompt
handed to heterogeneous agents that the model then repeats back to the
user.

That is fine for a single distribution and leaves a white-label build with
no seam. Several of these are reached at runtime along paths no UI gating
can intercept, and one of them is a prompt, so there is nothing to gate.

`src/constants/identity.ts` now holds all of them. Values are upstream's,
unchanged, and the default build's observable output is identical —
verified by building with this repo's own config and diffing what the CLI
prints:

    Usage: lh [options] [command]
    LobeHub CLI - manage and connect to LobeHub services
    complete -o nosort -F _lobehub_completion lh lobe lobehub
    lh - LobeHub CLI - manage and connect to LobeHub services

Two things that were already wrong, fixed on the way:

  - `skillLocator.ts` compared `pkg.name` against a hardcoded
    `'@lobehub/cli'`, so the package-root walk breaks for any fork or
    rename. It reads `cliPackageName` now, which is the same value from
    the same package.json.
  - `constants/auth.ts` exposed a single API-key env var name. It is a
    list now, most-preferred first, so a distribution can introduce its
    own prefix without breaking a variable a user already exported. Only
    USER-set variables moved; `LOBEHUB_SERVER`, `LOBEHUB_JWT` and the
    agent-run ids are injected by the desktop app and the server, so
    renaming those needs all three sides to move together and is left
    alone.

`man/roff.ts` also stops hardcoding `LH` and `@lobehub/cli` in the roff
header, taking them from the bin name and `pkg.ts` instead — the man page
described a command that would not exist under any other name.

* ♻️ refactor(api-key): make the key prefix a slot instead of a literal

`sk-lh-` was hardcoded in four places and asserted in four test files. It
is the one brand string a deployment's end users actually handle — they
copy it out of the UI, paste it into CI config, quote it in support
threads — so on a white-label distribution it undoes the rebranding
everywhere else.

It is also load-bearing rather than cosmetic. `validateApiKeyFormat` is
what the OpenAPI auth middleware uses to tell an API key from an OIDC
JWT, so generation and validation must read the same value or every
issued key fails auth as a malformed JWT. That is why the prefix is one
constant now, used by both.

`API_KEY_PREFIX` defaults to `sk-lh-`, so upstream's behaviour is
unchanged — verified by generating a key and round-tripping it through
the validator.

Two deliberate choices in the implementation:

  - Validation compares the prefix with `startsWith` and tests only the
    16-character suffix against a static pattern, rather than
    interpolating the prefix into a RegExp. The prefix is data flowing
    into a pattern, and escaping it correctly is a thing to get wrong;
    this way there is nothing to escape and the check stays correct for
    whatever prefix a distribution picks.
  - The tests derive their fixtures from the constant instead of
    hardcoding a spelling, so they pass for any prefix. Several of them
    fed a literal straight into `validateApiKeyFormat`, so they would
    have gone red the moment anyone overrode it — failing on the
    override rather than on a real defect.

`spec.ts`'s `bearerFormat` follows the constant too; it is the public
API document, so it was advertising a format the deployment no longer
issues.

* ♻️ refactor(cli): collect the completion word-index env var into identity.ts

CLI_COMPLETION_CWORD_ENV joins the other product-naming strings in
identity.ts. `LOBEHUB_COMP_CWORD` was the one string the earlier
identity refactor (a65fa6a9) missed — it's set by the generated
completion script and read right back by `__complete` in the same
process, so it never surfaces in help text and slipped past every
rendered-output check.

Purely internal plumbing (unlike CLI_API_KEY_ENV_NAMES), so no
back-compat list is needed: both sides always come from the one
installed binary.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* 🛡️ fix(api-key): fall back to sk-lh- when a distribution override omits the slot

A downstream `@lobechat/business-const` override replaces the whole module
instead of extending it, so one written before this slot existed simply
omits API_KEY_PREFIX rather than inheriting upstream's default — that
either fails typecheck or, if typecheck is skipped, crashes key generation
and validation on undefined.length.

Resolve it once in packages/utils/apiKey.ts with an explicit runtime
fallback to 'sk-lh-', and have every other consumer (openapi spec, tests)
import the resolved constant from there instead of the raw slot.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 15:43:24 +08:00
..