Public: anyone can read this; the crew can change it.

The terminal lists an app's variables, sets one and unsets one, never showing a value, as MCP `app_env` does #7277

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

An app's admin can now list, set and unset its environment variables from the terminal. They open *Variables* from the repository screen's `:` palette. The screen uses the same `listAppEnv`, `setAppEnvVar` and `deleteAppEnvVar` actions as the settings page and MCP `app_env`, and it never shows a value.

shipped production is serving it deploy 119f4d2

opened this

An app's admin can now list, set and unset its environment variables from the terminal. They open Variables from the repository screen's : palette. The screen uses the same listAppEnv, setAppEnvVar and deleteAppEnvVar actions as the settings page and MCP app_env, and it never shows a value.

What moved where

  • src/actions/app_env_words.ts (new) now holds the variable wording that lived only in src/web/pages/settings.ts: the scope words, the refusal for each error code, and the "could not be set" and "No variable by that name." messages. The settings page imports them, so the page and the terminal say the same thing. The page's behaviour is unchanged. Its "app's environment" doc comment, which had drifted above SCOPE_WORDS, is back on appEnvView.
  • src/tui/screens/appenv.ts (new):

- appEnvCommands adds the palette entry owner/name — Variables, only for a repository that is an app and only for its admin. - appEnvScreen lists each name with its scope and when it was set. - a adds or replaces a variable: a name, then the value in a masked field, then a scope (everywhere, production or previews). - d asks for a confirm, then unsets the variable under the cursor. - A refusal is shown wrapped at the top of the screen in the page's words, not cut off. - The value only goes from the masked field into the action call; it is never drawn, put in a toast or logged.

  • src/tui/screens/workspace.ts adds the entry to the repository screen's palette.
  • docs/APPS.md → Environment now names the terminal route and MCP app_env.

Look at: the set flow in appenv.ts, where the value passes through later() closures into setAppEnvVar.

Left alone: MCP app_env still reports a refusal by its error code and was not changed. promptScreen trims whitespace around a typed value, as every masked field in the terminal does.

Tests: test/tui-app-env.test.ts drives a headless terminal and checks:

  • adding a variable, with dots showing in the masked field;
  • the list shows the name and scope but not the value;
  • a bad name and a value over 64 KiB are refused in the page's words;
  • unsetting asks first: "Keep" changes nothing, confirming removes it;
  • a writer who is not an admin gets no palette entry and can set nothing.

It keeps every frame it draws and asserts that none of them, and none of the app.env_* events, contains the value. These pass alongside the existing app-env, settings-page, MCP (including mcp-size), palette and docs tests, the type check and design-lint.

To try: bun test test/tui-app-env.test.ts. In the terminal, open an app repository you administer, press :, type "Variables", then a to set one or d to unset one.

🤖 Generated with Claude Code

Closes #7232

commented

An app's admin can now list, set and unset its environment variables from the terminal. They open Variables from the repository screen's : palette. The screen uses the same listAppEnv, setAppEnvVar and deleteAppEnvVar actions as the settings page and MCP app_env, and it never shows a value.

What moved where

  • src/actions/app_env_words.ts (new) now holds the variable wording that lived only in src/web/pages/settings.ts: the scope words, the refusal for each error code, and the "could not be set" and "No variable by that name." messages. The settings page imports them, so the page and the terminal say the same thing. The page's behaviour is unchanged. Its "app's environment" doc comment, which had drifted above SCOPE_WORDS, is back on appEnvView.
  • src/tui/screens/appenv.ts (new):

- appEnvCommands adds the palette entry owner/name — Variables, only for a repository that is an app and only for its admin. - appEnvScreen lists each name with its scope and when it was set. - a adds or replaces a variable: a name, then the value in a masked field, then a scope (everywhere, production or previews). - d asks for a confirm, then unsets the variable under the cursor. - A refusal is shown wrapped at the top of the screen in the page's words, not cut off. - The value only goes from the masked field into the action call; it is never drawn, put in a toast or logged.

  • src/tui/screens/workspace.ts adds the entry to the repository screen's palette.
  • docs/APPS.md → Environment now names the terminal route and MCP app_env.

Look at: the set flow in appenv.ts, where the value passes through later() closures into setAppEnvVar.

Left alone: MCP app_env still reports a refusal by its error code and was not changed. promptScreen trims whitespace around a typed value, as every masked field in the terminal does.

Tests: test/tui-app-env.test.ts drives a headless terminal and checks:

  • adding a variable, with dots showing in the masked field;
  • the list shows the name and scope but not the value;
  • a bad name and a value over 64 KiB are refused in the page's words;
  • unsetting asks first: "Keep" changes nothing, confirming removes it;
  • a writer who is not an admin gets no palette entry and can set nothing.

It keeps every frame it draws and asserts that none of them, and none of the app.env_* events, contains the value. These pass alongside the existing app-env, settings-page, MCP (including mcp-size), palette and docs tests, the type check and design-lint.

To try: bun test test/tui-app-env.test.ts. In the terminal, open an app repository you administer, press :, type "Variables", then a to set one or d to unset one.

🤖 Generated with Claude Code

Approved

The terminal now lists, sets and unsets an app's variables through the same actions as the page and MCP app_env. Admin-gated, the value is masked and never drawn, and refusals use the page's words from the shared app_env_words.ts. The settings page is a pure move of wording, so there is no design-system change. The tests cover set, list, refusals, confirm-to-unset, the non-admin case, and a check that no frame or event holds the value. I could not exercise the terminal through curl, so I judged it from the source and the tests.

Two things I would do next:

  1. In appenv.ts, where() hard-codes "everywhere" next to SCOPE_WORDS.all. Derive it from the table so the two cannot drift. Also say in the unset confirm whether the app redeploys, in the same words the page's redeploying() uses, instead of the fixed "The app restarts without it."
  2. promptScreen trims the typed value, so a secret with leading or trailing whitespace is silently altered. Either say so in the value label or add a raw masked field.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs