tunablestunables version 0.2.0

CLI

The CLI

The tunables command checks your definitions, writes the files generated from them, reads and changes stored values through the same checks as the screens, and installs the agent skills. It is installed with the package:

npx tunables --help

Commands

CommandDoesNeeds a database
checkCheck definitions, .env.example and the Product ModelNo
envWrite .env.example from the deployment valuesNo
explainWrite field explanations from the Product ModelNo
model draftDraft the facts the Product Model lacksNo
syncSeed new settings with their defaultsYes
getPrint stored values, for the system or a scopeYes, or the app's API
setChange values, checked and recordedYes, or the app's API
historyShow a group's revisionsYes, or the app's API
restoreRestore a group to an earlier revisionYes, or the app's API
installInstall the agent skillsNo
updateUpdate installed skillsNo

Every command takes:

OptionDoes
-c, --cwd <path>Run from another directory
--config <file>Use this config instead of tunables.config.ts
-h, --helpShow help for the command
-V, --versionShow the CLI version

get, set and history take --scope workspace=acme,user=dana to work at a scope. get, set, history and restore take --url <url>, or read TUNABLES_URL, to go through your app's tunables API with TUNABLES_TOKEN as a bearer token instead of through connect; see Through your app's API.

A command exits 0 when it succeeds, 1 when what it checked does not hold or a change was refused, and 2 for a usage mistake, such as a missing config or an unknown group.

tunables.config.ts

The commands read tunables.config.ts (or .mts, .js, .mjs) at the root of your app. It is TypeScript, loaded as it is; nothing needs building first.

tunables.config.ts
import pg from 'pg'
import { defineConfig } from 'tunables/config'
import { fromPg, migrate } from 'tunables/postgres'
import { catalogs, effects, environment, groups, presets, units } from './server/settings'
import { makeTunables } from './server/tunables'

let pool: pg.Pool | undefined

export default defineConfig({
  groups,
  presets,
  catalogs,
  units,
  effects,
  environment,
  envExample: '.env.example',
  envHeader: 'Deployment values. Settings an administrator tunes live in the database.',
  productModel: 'product-model',
  explanations: 'server/tunables-explanations.ts',
  async connect() {
    pool = new pg.Pool({ connectionString: environment.get('DATABASE_URL') })
    await migrate(fromPg(pool))
    return makeTunables(fromPg(pool))
  },
  async disconnect() {
    await pool?.end()
  }
})
OptionWhat it is
groups, presets, catalogs, units, effectsYour definitions, as you pass them to createTunables. Enough for every command that needs no database.
environmentYour deployment values.
envExample, envHeaderWhere .env.example is (.env.example by default) and the lines it starts with.
productModelYour pdd-kit Product Model. Defaults to product-model when that directory exists.
explanations, explanationsImportWhere tunables explain writes the explanations module, and what it imports its type from (tunables by default).
connectYour app's tunables over its own store. Called only by the commands that read or change stored values, unless they are given --url.
disconnectClose what connect opened, so the command can exit.

Importing the config must not connect to anything: open the connection inside connect, so check and the other offline commands, and the agent skills that run them, work without a database.

Who a change is recorded as

A change from the terminal is recorded with your username and (terminal), such as ada (terminal), and needs a --reason, which is kept in the history. Through connect, the commands reach everything the database does, and your authorize is not asked. With --url, the change is recorded as the actor your app names for TUNABLES_TOKEN, and your authorize decides what it may do.

examples/tasklane/tunables.config.ts is a complete config: in memory, or on Postgres when DATABASE_URL is set.

Update checks

A globally installed CLI checks npm for a newer version at most once a day, in the background, and asks once per new version whether to update. It never checks in CI, without a terminal, when run through npx, or as a project dependency, whose version your lockfile decides. Set TUNABLES_NO_UPDATE_CHECK=1 to turn it off.