Skip to main content

npm

Source

The typed stars.config.* schema and loader live in @wolfstar/http-framework (@wolfstar/http-framework/config). This package only consumes it to drive its commands:
  • stars dev builds the project, starts the bot, restarts it (or leaves the change to the bot’s hot reload) and shows what is happening in a full-screen dashboard with filterable log channels and levels (or a compact panel, or plain logs).
  • stars build runs the configured build tool once.
  • stars info prints the resolved configuration and environment (--json for scripts).
  • stars codegen runs the configured code generators: i18next types and the typed options of your commands (--check for CI).
  • stars prepare generates the TypeScript configuration in .stars/ and the auto imports declaration file (--check for CI).
  • stars typecheck regenerates .stars/ and type-checks every project once.
  • stars commands inspects, compares, deploys, and cleans the application commands Discord has deployed.
  • stars doctor checks that the project is ready: runtime, framework, credentials, interactions endpoint, generated files.
  • stars completions prints the shell completion script for bash, zsh, or fish.
Everything is driven by a typed stars.config.ts file.

Installation

Installing @wolfstar/http-framework already gives a project the stars binary. You only need this explicit install to use the package’s programmatic exports (loadStarsConfig, diagnostics) or to pin its version independently of the framework’s. Projects scaffolded with @wolfstar/create-http-framework come with @wolfstar/cli, a stars.config.ts file and dev/build scripts already wired up.

Configuration

stars.config.{ts,mts,cts,js,mjs,cjs} is defined by @wolfstar/schema and re-exported by @wolfstar/http-framework. See the framework page for the full option reference (root, entry, build, dev, codegen, env, hooks) and the defineConfig helper. stars looks for the file in the working directory (--config <file> overrides it, --cwd <dir> changes the working directory). It passes configuration diagnostics through as exit code 2 and prints the offending option path and a hint to the terminal.
stars dev’s display URL needs no configuration either. It follows dev.env.HTTP_PORT/PORT, the process’s HTTP_PORT, the project’s env-file HTTP_PORT/PORT, or 3000, the same way Vite’s and Nuxt’s dev servers do. An ambient process PORT is ignored in framework mode. localhost is swapped for 127.0.0.1 at startup if that is what is actually reachable. An explicit dev.url overrides only the display URL and health checks, so it can point at a local proxy or a remote tunnel. See Port conflicts. The resolved configuration is also available programmatically, exactly as the commands see it (re-exported from this package for convenience, or import it directly from @wolfstar/http-framework/config):

Commands

stars info

Prints the resolved configuration and environment. Pass --json for scripts. Besides the build, codegen, env, hooks, and experimental flags, the output lists:
  • Dev: the dev options in effect, including the log file, the per-run log directory and how many files it keeps, the layout, the channels and levels the dev UI starts with, the command refresh mode, and whether changes are left to the bot’s hot reload.
  • Modules: the installed modules and what they registered: runtime plugins, pieces directories, locales, and client hooks. Below compatibility version 7, a by name row lists the @wolfstar/plugin-* packages activated through their /register import because no module covers them.
The tunnel row names the configured provider and domain, for example ngrok quick tunnel on bot.ngrok.app.

stars dev

Watches the sources through the configured build tool (tsdown programmatically, configured from your stars.config, tsc -b --watch, or a plain file watcher for JavaScript projects), starts the bot after the first successful build and restarts it after every following one. Failed builds keep the previous process running and wait for the next change; a crashed bot waits for the next change or a manual restart. The bot runs as a child node process with STARS_DEV=1 and NODE_ENV=development in its environment. When the bot enables the framework’s hmr option, stars dev leaves changes to pieces to the bot instead of restarting it. See Hot reload. dev.layout in stars.config, or --layout, picks the interactive UI: Press v to switch between the two while a session runs.

Terminal style

tui.style in stars.config, or --style, picks the presentation by name:
dev.layout is still supported. The first setting found wins, in this order: --style, --layout, tui.style, then dev.layout. Without any of them, stars dev keeps the automatic choice between the dashboard and the panel. --no-tui and STARS_TUI=plain always use plain logs. An explicit --tui overrides tui.style: 'none', but --style none always uses plain logs.

Startup checks

Before stars dev creates the builder or writes generated files, it checks that:
  • The output, generated-file, and log directories can be written. A directory that can’t be created or written, or a file in its place, fails with DEV_DIRECTORY_UNWRITABLE.
  • The bot’s listener port is free. A bind error other than an occupied port, such as a missing permission, fails with PORT_CHECK_FAILED.
  • The listener port is an integer from 1 to 65535. An invalid value fails with INVALID_PORT.
Listener ports are only validated when stars dev starts, so an unrelated shell value doesn’t stop stars build, stars info, or stars doctor from inspecting the project. The framework listener binds HTTP_ADDRESS (default 0.0.0.0). Its port comes from dev.env.HTTP_PORT or dev.env.PORT, then the process’s HTTP_PORT, then the env file’s HTTP_PORT, then the env file’s PORT (only when dev.url isn’t set), or 3000. In Nitro mode, the listener uses NITRO_HOST/HOST and NITRO_PORT before PORT.

Port conflicts

When the listener port is occupied, dev.portConflict or --port-conflict decides what happens: The prompt also works with plain logs on a terminal. CI, piped input, and TERM=dumb never wait for an answer. Cancelling creates no builder and no generated files. --port <port> (or -p <port>) sets the listener port for one session:
--port and a port picked from the prompt set the child process’s HTTP_PORT, PORT, and NITRO_PORT for this session only. stars dev doesn’t rewrite your env files or global variables. An inferred display URL follows the chosen port. An explicit dev.url (and dev.health) stays unchanged, path and query included, and its host and port are never probed as the bot’s listener. For example, dev.url: 'https://localhost/bot' can keep pointing at a proxy on port 443 while the bot starts on 3000 or --port 4000.

Listen address

--host <address> sets the address the bot listens on, like Nuxt’s --host. It accepts an IP address or a hostname. Use it to expose the dev server on your network or to bind a specific interface:
A bare --host listens on every interface (0.0.0.0). stars dev passes the address to the bot as HTTP_ADDRESS, and as NITRO_HOST and HOST in Nitro mode. The Vite and Nitro dev servers bind to it, and the port check probes that address. An inferred display URL follows a concrete address. A wildcard address (0.0.0.0 or ::) or an explicit dev.url leaves the display URL unchanged. An invalid address fails with INVALID_OPTION. Port probes release their sockets before startup, so another process can still claim a free port before the bot binds it. --tunnel opens the public tunnel at startup for one session, whatever dev.tunnel says. When dev.tunnel is off, stars dev opens a cloudflared quick tunnel. Otherwise it uses the dev.tunnel provider. --no-tunnel keeps the tunnel closed at startup even when dev.tunnel enables it. Pressing t still opens it later. Leave both flags out to let dev.tunnel decide. See Type checking, tunnel and logs.

Dashboard

The dashboard is the default on a TTY of at least 90×20. It is a full-screen view in the alternate buffer.
The sidebar shows the state of the session (starting, building, running, stopped, error), the framework version, the uptime, the port, the tunnel, and where the logs are written. Below that are the channel and level filters of the stream. The stream prints one line per entry: a level badge (T D I W E), the time, the channel, and the message with URLs, paths, names, and numbers highlighted. An entry with detail lines, such as the commands that were loaded or the paths that are watched, is shown as a block between two rules. The stack frames the bot prints after an error are folded under that error and count as one entry. The build percentage follows actual build milestones, not a timer.

Log channels

Each log entry has a channel that says what it is about, so you can switch each one off: The last five channels come from the bot itself. stars dev preloads a small bridge into the bot (node --import) that reports its events over an IPC channel instead of guessing from stdout. The bridge needs @wolfstar/http-framework 6.1 or later, resolved from the project. Without it, or with a build that bundles the framework (as Vite and Nitro do), those channels stay empty and everything else works as before. A plugin can log on a channel of its own:
Entries have one of five levels: trace, debug, info, warn, and error. trace is hidden at start because it logs one line per request. dev.logs.channels and dev.logs.levels in stars.config choose what a session starts with. The log file always receives every entry.
--channel and --level override them for one session. --channel accepts a comma-separated list and can be repeated. --level shows that level and every more severe one.

Keyboard shortcuts

The channel and level keys (←/→, Tab, Space, s, a, Enter, b, g/G, /) only apply to the dashboard. The panel shares every other key.

Hot reload

When the bot runs with the framework’s hmr option enabled, it tells stars dev which directories it watches. A build that only changed pieces in those directories is then left to the bot. The process, its HTTP server, and its connections stay up, and the hmr channel shows what was reloaded. A piece is a file the bot loaded one from, or a new file that could be one. Any other change still restarts the bot: the entry, a shared module, a locale, or a helper next to the pieces (such as _shared.js, or any file no piece came from), which the bot imports once and cannot replace. A bot without hmr, or one that stopped its reloader, is always restarted. stars dev judges what a build changed by content, since a bundler such as tsdown rewrites its whole output on every rebuild. Set dev.hmr: false to always restart the bot:

Command refresh

The bot reports the application commands it registers when it starts and after every hot reload. When they differ from what it reported before, stars dev reports the change on the commands channel. dev.commands.refresh picks what happens next:
Without the interactive UI (--no-tui, CI), 'prompt' only reports the change. An open question survives a restart of the bot. A y given while the bot is stopped or restarting is carried out once it listens again. A refresh also empties a guild whose last command was removed since the last deploy. Command refresh needs @wolfstar/http-framework 6.1 or later.

Panel

The panel is used with tui.style: 'nuxt-style', --style nuxt-style, dev.layout: 'panel', --layout panel, the v key, or a terminal smaller than 90×20. It is a bottom-aligned panel in the normal buffer, following the layout and keyboard conventions of Nuxt CLI’s dev TUI. It shows a Stars wordmark, aligned URLs, a 20-cell progress bar with elapsed time, status, and shortcuts. Logs are folded away: l opens them and e jumps to the last error. Once ready, the bar gives way to diagnostics and the header reports the load time. Application output (including its banner), build-plugin output and diagnostics stay in the bounded log history and .stars/dev.log. tsdown’s entry list and output-size table are suppressed. Closing an overlay restores the view under it without duplicating output in scrollback. running (READY in the panel) reports process state unless dev.health is configured; it does not certify that every application plugin loaded successfully. In the log browser (l): arrows or j/k select, PgUp/PgDn move a page, g/G go to the beginning/follow the tail, e/w/a filter errors/warnings/all, c/b/r toggle CLI/build/runtime sources, / searches, x clears, and Enter/y copies the selected line on terminals supporting OSC 52 clipboard writes. q, Esc or the view’s own shortcut closes an overlay rather than quitting the session. Replace the default wordmark in stars.config.ts (up to four lines are displayed, clipped to the terminal width):
dev.banner also accepts a string containing newlines, or false to hide the wordmark. Omit it for Stars branding. The dashboard shows the project’s name instead of the wordmark. For the application’s standalone banner outside the TUI, use createStarsBanner from @wolfstar/start-banner.

Plain mode

Plain mode prints prefixed lines instead: the channel, then the message, with detail lines indented under it. The bot’s own output is passed through untouched. It is selected by --no-tui, --style none, tui.style: 'none', STARS_TUI=plain, redirected input/output, CI, TERM=dumb, or terminals smaller than 40×10. STARS_TUI=1 overrides CI/size checks, never redirected streams or a dumb terminal. Both modes honour NO_COLOR/FORCE_COLOR; STARS_REDUCED_MOTION=1 freezes the logo/spinner but keeps the elapsed clock. Both stop the bot cleanly on SIGINT/SIGTERM. SIGUSR2 restarts the bot (not on Windows).

stars codegen

Runs the code generators that codegen in stars.config enables and writes their files. Pass --check in CI to fail with CODEGEN_OUTDATED when a file is stale instead of writing it. Pass --json to print { check, results }. Pass --only i18n or --only commands to run a single generator. Repeat the flag or comma-separate the values to run several (--only i18n,commands).
  • codegen.i18n types the i18next resources with @wolfstar/i18next-type-generator. It is on by default when src/locales/en-US exists.
  • codegen.commands types the options of every command from its builder. It is off by default. true writes src/@types/commands.d.ts, and { output } picks another file, relative to the project root.

Typed command options

With codegen.commands enabled, the generated file augments CommandOptionsRegistry with one entry per command path ('ping', 'math add', 'subscriptions twitch add'):
Handlers type their options with Command.OptionsOf<'path'>, so you no longer maintain a hand-written interface Options that can drift from the builder. A renamed option, a flipped required, or a changed type stops compiling:
The generated types follow what the framework resolves at runtime:
  • Every option has the shape the handler receives (user is TransformedArguments.User, role is APIRole, and so on).
  • A required option is not optional.
  • choices become a literal union.
  • channel_types narrow a channel option.
  • A localized command is keyed by its default name, the one the handler receives.
  • Context menu commands have no options and are skipped.
  • A path registered more than once (a command restricted to several guilds) keeps its first definition.
For an autocomplete handler, Command.AutocompleteArguments<Command.OptionsOf<'math add'>> types focused from the same entry.

Build first

stars codegen reads the commands from the built bot, the same way stars commands diff does, so run stars build first. Loops and factories in your builders need no special care, because the types come from what the bot actually registers. The CLI starts the bot in a mode that loads its pieces, reports its registry, and exits before it listens or talks to Discord. It never calls client.login. The bot still runs its own setup, so the values it reads while starting (DISCORD_CLIENT_ID, DISCORD_PUBLIC_KEY, and so on) must be set. In CI, where the real secrets are not available, pass placeholders:

Run one generator in CI

--only lets a CI job run the generator it can. The i18n types only read the locale files, so you can check them without the bot’s secrets or a build. The command types need the built bot and the values it reads while starting:
For example, a GitHub Actions step that checks the i18next types without any secrets:
A generator named in --only must be enabled. --only commands with codegen.commands off fails with CODEGEN_GENERATOR_DISABLED instead of passing without checking anything. An unknown name fails with INVALID_OPTION. stars dev does not generate command types, and neither does stars prepare, which has no build to read from yet. Run stars codegen after you change an option, and stars codegen --check in CI to keep the file up to date.

stars commands

Lists what Discord currently has deployed, which is not necessarily what the project registers today: renamed and removed commands stay until something deletes them.
It reads DISCORD_TOKEN and DISCORD_APPLICATION_ID (or APPLICATION_ID) from the environment or the project’s .env, the same place the bot reads them from. clean deletes deployed commands, so on a terminal it opens a wizard (a checklist of what is deployed, then a confirmation). Anywhere else it refuses to run without --yes or --name.

Remove a subcommand or group

--name also takes the path of a subcommand or a group: settings server removes the server subcommand (or group) of /settings, and settings group sub removes one subcommand inside a group. Use it to drop a single subcommand you no longer register without deleting the whole command. Discord has no endpoint to delete a subcommand, so clean fetches the deployed command, drops that entry from its options, and sends the command back with a PATCH. The parent command and its other subcommands stay. Paths under the same command share one request.
  • A value that exactly matches a deployed command’s name deletes that command first. This keeps context menu commands with spaces in their names, such as Report User, working.
  • A group left without subcommands is removed along with its last one, since Discord rejects an empty group.
  • A command cannot exist without a subcommand. Removing a command’s last subcommand deletes the whole command. The confirmation says so, and --yes accepts it.
  • A path that matches nothing fails with COMMAND_NOT_FOUND.
  • The wizard still lists whole commands only. Pass --name to remove a subcommand or group.
With --json, the output lists whole commands under deleted and subcommands or groups under removed:
clean reads the deployed command and writes it back. A deploy that runs between the read and the write is overwritten.

Compare and deploy

diff and deploy compare what Discord has deployed with what the project defines:
Commands are declared with builders and decorators that only exist once the bot’s modules ran, so stars asks the bot. It starts the built entry with the dev bridge, which loads the pieces, reports the registry, and exits before the bot listens or talks to Discord. Run stars build first. This needs @wolfstar/http-framework 6.1 or later. The bot is started the way stars dev starts it (the same env files, after the env:options hook), in the caller’s NODE_ENV, or development when unset. Run NODE_ENV=production stars commands deploy to deploy what a production start registers. STARS_DEV is not set, since this is not a dev session. A command counts as changed when what the project defines no longer matches what is deployed. Fields Discord fills in on its own (id, version, defaults such as nsfw: false) are ignored. diff --check exits with code 1 and raises COMMANDS_DIFFER when anything differs. deploy uses Discord’s bulk overwrite, so a deployed command the project no longer defines is deleted. That is why it asks first. Outside a terminal, or with --json, it refuses to run without --yes. To deploy on every start instead of by hand, enable commands: { register: 'on-start' } in the Client options. The bot then writes only the differences after it listens, and keeps unknown deployed commands unless removeUnknown is set. See Sync commands on startup.

stars typecheck

Type-checks the project once, the way nuxt typecheck does. stars typecheck first regenerates .stars/ (the same work as stars prepare), then runs the project’s checker once on each project. The checker is dev.typecheck.checker: golar or tsc.
From future.compatibilityVersion 6, the projects are:
  • .stars/tsconfig.app.json and .stars/tsconfig.node.json for tsdown and vite builds.
  • The project’s own tsconfig plus .stars/tsconfig.node.json for tsc and none builds.
  • Only the configured tsconfig when dev.typecheck.tsconfig is set.
On versions 5 and below, stars typecheck checks the single tsconfig. Use it instead of tsc -p tsconfig.json: a solution-style root tsconfig.json has files: [], so a bare tsc -p on it checks nothing. Use it as the project’s typecheck script:
stars typecheck exits with 1 and the TYPECHECK_FAILED diagnostic when any project has type errors. It lists the failing projects. stars info lists the projects it checks on the typechecks row.

stars doctor

Checks what a project needs before stars dev can do its job, and says what to do about each problem:
It covers:
  • The Node.js version, and the project’s engines.node.
  • The configuration and its warnings.
  • The framework, and whether it is recent enough for the dev bridge.
  • The entry and the build output.
  • DISCORD_TOKEN, DISCORD_PUBLIC_KEY, and the application id.
  • Whether the dev port is free.
  • The tunnel. With provider: 'ngrok', it warns when @ngrok/ngrok is not installed in the project or when NGROK_AUTHTOKEN is not set.
  • Whether .stars/ is stale. It names each generated file that is out of date.
  • Whether the root tsconfig.json still extends ./.stars/tsconfig.json (TSCONFIG_LEGACY_EXTENDS).
  • Whether a varlock .env.schema and env.loader agree (the varlock check).
  • The plugins registered by name. Below compatibility version 7, the plugins check lists the @wolfstar/plugin-* packages activated through /register. On version 7, the config check warns with PLUGINS_NOT_REGISTERED about any of them left out.
--online also asks Discord whether the token works and where the application sends its interactions. stars doctor changes nothing unless you pass --fix. --json prints { ok, checks } for scripts. The exit code is 1 when a check fails, with the DOCTOR_FAILED diagnostic.

The varlock check

The varlock check compares the project’s varlock setup with env.loader in stars.config: A project that sets env.path without env.loader: 'varlock' is not checked, because the runtime only picks varlock by itself when no env.path is set.

Fix the configuration

--fix fixes what stars doctor can fix. Today that is a project where varlock loads the environment but stars.config does not set env.loader. stars doctor --fix adds env: { loader: 'varlock' }:
  • Without a stars.config, it creates stars.config.ts the way @wolfstar/create-http-framework scaffolds it.
  • With a literal defineConfig({ ... }) or exported object, it edits that object.
  • With a configuration it cannot edit safely (a spread, a function, or an env that is not a literal), it changes nothing and prints the line to add by hand.
stars doctor --fix asks before it writes. Pass --yes (-y) to answer for scripts. It never writes in CI, with --json unless you pass --yes, or without a terminal to ask in.
stars prepare, stars build, stars dev, and stars info only report this case, with the VARLOCK_LOADER_IMPLICIT and VARLOCK_NOT_INSTALLED warnings.

stars completions

Prints a shell completion script for bash, zsh, or fish. Add it to your shell’s startup file:
The script is generated from the commands the CLI registers. It completes every command, subcommand, and flag that --help lists, with their short forms (-c) and the --no- form of flags that are on by default (--no-tui).

Type checking, tunnel and logs

Three dev options round out the dev loop (all documented in @wolfstar/http-framework):
  • dev.typecheck: true runs a type checker next to the bot and reports type errors on the UI’s types channel, without ever blocking a build or a restart. This is useful when building with tsdown, which does not type-check. dev.typecheck.checker picks which one: tsc (the project’s TypeScript, watch mode), golar (golar tsc, watch mode), tsz (the tsc-compatible checker, re-run after every build since it has no watch mode), or auto. auto is the default and uses golar when the project depends on it and tsc otherwise.
  • Pressing t opens and closes a quick tunnel without configuration. It uses cloudflared, or the dev.tunnel.provider you set. dev.tunnel: true opens it at startup so Discord can reach the bot’s interactions endpoint from the internet. stars dev --tunnel does the same for one session, whatever dev.tunnel says, and --no-tunnel keeps it closed at startup (t still opens it). In plain mode (--no-tui), where there is no t key, the CLI prints the URL on the tunnel channel. A string is an https URL you already serve, which the CLI only probes. dev.tunnel.updateEndpoint writes the URL to the Discord application, and is opt-in because it edits a live application. See Tunnel providers to use ngrok instead of cloudflared.
  • dev.logFile (default .stars/dev.log) mirrors the session’s logs to disk, so you can read a run back after the terminal UI is gone. Set it to false to disable it. The file is truncated on every run.

Tunnel providers

dev.tunnel.provider picks the service that opens the quick tunnel: cloudflared stays the default, so dev.tunnel: true and existing configurations behave as before. Use ngrok when you want a stable hostname: set dev.tunnel.domain to a domain reserved in your ngrok account, and the URL survives restarts. To use ngrok:
  1. Install the official SDK in your project. @ngrok/ngrok is an optional peer dependency of the CLI, loaded only when the ngrok provider is used.
  2. Set NGROK_AUTHTOKEN. The CLI looks in dev.env, then in the environment, then in the project’s env files, the way the bot does. The env files are read again each time the tunnel opens, so pressing t after adding the token works without restarting stars dev.
  3. Set the provider, and optionally a reserved domain:
When the token is missing, opening the tunnel fails with the NGROK_AUTHTOKEN_MISSING diagnostic on the tunnel channel. Run stars doctor to check both the package and the token. provider and domain only apply to a tunnel the CLI opens. Setting either next to url, or setting domain without provider: 'ngrok', fails with TUNNEL_OPTION_CONFLICT. An unknown provider fails with INVALID_CHOICE.

Log files

To keep earlier runs, set dev.logs.dir (for example 'logs'). stars dev then also writes one file per run, dev-<timestamp>.log, in that directory. dev.logs.keep (default 10) sets how many per-run files stay, the current run included. Older files are deleted when a run starts.
Each line of a log file is <ISO time> <level> <channel> <message>, with the detail lines of an entry indented under it. Log files never filter entries out.

The build

tsdown is configured from stars.config, and a base project configures nothing: the entry’s directory, one output file per source file, ESM on Node, build.outDir, the tsconfig (src/tsconfig.json or tsconfig.json), the extension build.output implies, sourcemaps, unbundled dependencies, Nuxt’s ~/@/~~/@@ alias prefixes and the auto imports plugin, and copying src/locales to dist/locales are all filled in (the framework README lists every default). The tsdown block is for what they cannot know: Every @wolfstar/plugin-* package listed in the project’s dependencies or optionalDependencies is activated automatically in bundler builds. stars injects its /register side-effect entrypoint before the application entry, so projects do not need to maintain bare imports such as import '@wolfstar/plugin-i18next/register'. Packages used only for development are intentionally not activated from devDependencies. stars injects <name>/register only when the installed package’s exports map defines a ./register subpath. Library plugins without that entrypoint, such as @wolfstar/plugin-cache, @wolfstar/plugin-gateway, and @wolfstar/plugin-sharder, are skipped, and you import them directly. A package that is not installed, or that declares no exports, is still injected so a missing install surfaces as a resolution error. If a build fails with ERR_PACKAGE_PATH_NOT_EXPORTED on a library plugin, upgrade to @wolfstar/cli@2.0.2 or newer, or refresh your lockfile so it resolves @wolfstar/vite-server@0.2.2 or newer. With future.compatibilityVersion: 7, stars no longer injects any /register import. Only the packages in modules, and the modules they install, are registered.
With future.compatibilityVersion: 3 (end-of-life) an existing tsdown.config.* still drives the build and the block is merged over it (values from stars.config win, plugins are appended); from 4 on the block is the whole configuration. The vite block works the same way for build.tool: 'vite'. stars info shows which file the build is configured from and which options the block sets.

Auto imports

With the tsdown build tool, stars wires the autoImports() plugin into the build, so the exports of every package in imports.presets are usable without an import statement. The default presets are @wolfstar/http-framework, @wolfstar/decorators, and @wolfstar/env-utilities. stars scans each preset from the project’s own node_modules and skips packages that are not installed. Once you add @wolfstar/decorators to your dependencies, ApplyOptions and the other decorators are auto-imported.
Setting imports.presets replaces the default list, so keep @wolfstar/decorators in it when you customize presets. stars prepare regenerates the auto imports declaration file after you install or remove a preset.

Compatibility version

future.compatibilityVersion selects the legacy or current defaults, the way Nuxt’s own compatibility setting does (see the framework README for the full reference): Version 7 is an opt-in preview, not the default. On version 7, stars build, stars prepare, stars dev, stars info, and stars doctor warn with PLUGINS_NOT_REGISTERED about an installed plugin that no module covers. Version 3 is end-of-life. It still works, but every command prints a COMPATIBILITY_VERSION_EOL warning, and the next major removes it.

Environment and hooks

env mirrors the options of setup() from @wolfstar/env-utilities. stars calls setup() with them as the first import of the built entry, before plugin registrations and the bot’s own modules. tsdown, Vite and Nitro builds get it through the entry transform. With build.tool: 'tsc' or 'none', only stars dev preloads it (node --import). Nitro leaves it off unless you set env explicitly. hooks registers CLI lifecycle hooks run with hookable: config:resolved, env:options, prepare:before/prepare:done, builder:created, tsdown:options, build:before/build:done, and dev:start/dev:restart/dev:close. They run in the CLI process, never in the bot. stars build and stars dev run them in the same order.
With compatibility version 6, when the project has a varlock .env.schema, an installed varlock, and no env.path or env.loader, the CLI resolves the loader to varlock the way the runtime does. stars dev and stars doctor then read the variables through varlock load. See How the CLI follows varlock. stars info lists the env options and the registered hooks. See the framework reference for every env option and hooks for each hook’s arguments and timing.

Experimental flags

experimental in stars.config.* turns on work that is still landing (see the framework README for the full reference): stars info prints which flags are on.

Exit codes

Generated TypeScript configuration

The generated compiler options combine @sapphire/ts-config, @sapphire/ts-config/extra-strict, and @sapphire/ts-config/decorators. The CLI loads these presets and writes their options directly into the file, so consumers do not need to install Sapphire. This enables strict checks, explicit overrides, and legacy decorators with metadata. Stars targets ES2022, skips dependency declaration checks, and stores incremental build information inside .stars/. Tsdown and Vite use ESNext/Bundler with noEmit; tsc retains Sapphire’s Node16 emit settings. Project compiler options can override these defaults. Bundler builds also follow Nitro’s TypeScript configuration: forced module detection, isolated modules, verbatim module syntax, JavaScript sources, .ts import extensions, package.json imports, and ESNext/DOM libraries. Use import type and export type for type-only dependencies. These options apply to tsdown and Vite; tsc keeps its emit-compatible settings. Sapphire’s decorator options and the ES2022 target remain in effect. This does not enable Stars’ experimental Nitro runtime integration. From future.compatibilityVersion 6, stars prepare writes two files, the way a Nuxt 4 project has them:
  • .stars/tsconfig.app.json covers the bot: everything under the entry’s directory plus the auto imports declaration, with the ~/@/~~/@@ paths and the options above. tsdown builds with it.
  • .stars/tsconfig.node.json covers the files Node runs without a bundler: stars.config.*, vitest.config.*, tsdown.config.*, vite.config.*, and scripts/**. It uses NodeNext resolution, types: ["node"], and no DOM library. It has no aliases, auto imports, or decorators, and leaves the entry’s directory to the app project.
Your project’s tsconfig.json is solution-style. It only references the two generated files:
New tsdown and Vite projects already ship this file, a typecheck script, and stars prepare in postinstall. Because the root has files: [], type-check with stars typecheck instead of tsc -p. stars dev’s dev.typecheck watches the app project. A root tsconfig.json that still extends ./.stars/tsconfig.json gets a TSCONFIG_LEGACY_EXTENDS warning from stars prepare and stars doctor. Replace it with the file above, or keep future.compatibilityVersion at 5. On versions 5 and 4, nothing changes. stars prepare writes the single .stars/tsconfig.json, which you extend from your project’s tsconfig.json:
Keep your existing compiler options alongside extends. stars dev and stars build also regenerate the generated files. For tsdown builds, @/ and ~/ resolve to the entry file’s directory (normally src/), while @@/ and ~~/ resolve to the project root. Filesystem aliases in stars.config.ts’s tsdown.alias are included too, with custom values taking precedence. Legacy builds using a separate tsdown config only include aliases declared in stars.config.ts. Other build tools do not get tsdown aliases, since TypeScript alone does not rewrite imports. The generated app config includes source files and the auto imports declaration, including a custom imports.dts location. Explicit include or compilerOptions.paths in your own tsconfig replace the inherited values; remove manually duplicated paths to use the generated aliases. Generation works with imports: false too. Use stars prepare --check to check the generated files without writing them. It fails when any of them is out of date. Do not edit anything in .stars/ by hand; keep .stars/ ignored by Git and run stars prepare after installing dependencies on a fresh checkout.

Server integrations

The Vite and Nitro builders live in dedicated packages — @wolfstar/vite-server and @wolfstar/nitro-server. The CLI loads the selected integration lazily, passes the resolved stars.config, and supplies project dependency loading and plugin registration through BuilderContext from @wolfstar/schema. Neither server package depends on the CLI or framework. Existing experimental flags, presets, output directories, and stars dev/stars build commands are unchanged; install Vite and Nitro in the consuming project as before. See CLI Errors for every diagnostic code stars can raise.