Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9d9033d
feat(launcher): add named envs with --env and envs list|new
jals1212 Oct 13, 2026
9218f6b
feat(shell): show the active named env in the header and Status card
jals1212 Oct 13, 2026
b45cc25
fix(uninstall): protect named envs and prove them in a bundled install
jals1212 Oct 13, 2026
a7a827b
feat(shell): show the named env as its own Status group above Project
jals1212 Oct 13, 2026
639b330
docs(odd): track named environments
jals1212 Oct 13, 2026
aacd381
fix(launcher): label an explicit --home env, use the injected env, an…
jals1212 Oct 13, 2026
e445000
feat(launcher): manage named envs from a full-screen envs TUI
jals1212 Oct 13, 2026
79f44e5
feat(launcher): give the envs gate Gentle's visual identity
jals1212 Oct 13, 2026
a92876b
feat(launcher): list the install's base home in the envs gate
jals1212 Oct 13, 2026
6bb583e
fix(launcher): say a home is set up on first open instead of showing …
jals1212 Oct 13, 2026
7311b66
fix(launcher): name the base home without a marker that needs a legend
jals1212 Oct 13, 2026
81b0090
fix(launcher): mark the default env like /gentle:profiles markers, no…
jals1212 Oct 13, 2026
df75804
fix(launcher): mark the default in envs list as (default), not a lead…
jals1212 Oct 13, 2026
ace01f1
fix(launcher): show text from user files in the envs gate without con…
jals1212 Oct 13, 2026
7fc7fcc
test(launcher): drive the envs manager through a simulated TTY to cov…
jals1212 Oct 14, 2026
26d2234
fix(launcher): clean up a failed envs new, show the env in the regula…
jals1212 Oct 15, 2026
870b98d
Merge branch 'feat/named-envs-core-v2' into feat/named-envs-gate-v2
jals1212 Oct 15, 2026
96cd724
fix(launcher): roll back a failed env rename and keep '..'-prefixed n…
jals1212 Oct 15, 2026
8220a2a
fix(shell): keep the env when the bar narrows and guard a symlinked n…
jals1212 Oct 15, 2026
9cb192e
Merge branch 'feat/named-envs-core-v2' into feat/named-envs-gate-v2
jals1212 Oct 15, 2026
3d4d746
Merge remote-tracking branch 'origin/main' into feat/named-envs-core-v2
jals1212 Oct 18, 2026
177f75d
Merge branch 'feat/named-envs-core-v2' into feat/named-envs-gate-v2
jals1212 Oct 18, 2026
17d905c
fix(launcher): accept --env only for a real env directory
jals1212 Oct 18, 2026
74402df
fix(uninstall): refuse to remove a folder that holds the named envs s…
jals1212 Oct 18, 2026
bd664d8
Merge branch 'feat/named-envs-core-v2' into feat/named-envs-gate-v2
jals1212 Oct 18, 2026
f72f3b9
fix(shell-bar): keep the env over the brand in the narrowest header
jals1212 Oct 18, 2026
486787b
fix(uninstall): guard every symlink on the named envs path and '..'-n…
jals1212 Oct 18, 2026
778bfe6
Merge branch 'feat/named-envs-core-v2' into feat/named-envs-gate-v2
jals1212 Oct 18, 2026
54bcd4e
test(shell-bar): cover the narrowest header keeping the env beside th…
jals1212 Oct 18, 2026
4addd84
test(launcher): use a junction for the --env link case on Windows
jals1212 Oct 18, 2026
4d4cf6f
Merge branch 'feat/named-envs-core-v2' into feat/named-envs-gate-v2
jals1212 Oct 18, 2026
42317f7
test(uninstall): run the bundled '..envs' case only where the fixture…
jals1212 Oct 19, 2026
4c5a3af
Merge branch 'feat/named-envs-core-v2' into feat/named-envs-gate-v2
jals1212 Oct 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
383 changes: 383 additions & 0 deletions bin/gentle-shell.mjs

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/gentle-shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ At 140 columns or wider, fullscreen defaults to a live header over a transcript-
✿ Gentle Shell ⟡ ~/work/gentle-pi main ⟡ gpt-5.5 · medium · team ctx ▰▰▰▰▱▱▱▱ 45% ⟡ $9.49 sub
```

- The header carries live session data: session identity on the left (brand, cwd, branch, dirty count, model · effort · profile) and the two live counters right-aligned (the context gauge and session cost). The effort reads `auto` for NaN models that manage their reasoning depth themselves (DeepSeek V4 Flash, Qwen 3.8 Flash, MiMo), with a one-time notice explaining that the selected level does not change the depth. It never shows the working/thinking state or extension statuses — those stay in the prompt indicator and the Status surface. When the terminal is too narrow for everything, segments give way in a fixed order — profile, then effort, then the whole cwd/branch/dirty group — before the counters are touched; below that, only the brand survives, and below that the header renders nothing.
- The header carries live session data: session identity on the left (brand, the named env when launched with `--env`, cwd, branch, dirty count, model · effort · profile) and the two live counters right-aligned (the context gauge and session cost). The effort reads `auto` for NaN models that manage their reasoning depth themselves (DeepSeek V4 Flash, Qwen 3.8 Flash, MiMo), with a one-time notice explaining that the selected level does not change the depth. It never shows the working/thinking state or extension statuses — those stay in the prompt indicator and the Status surface. When the terminal is too narrow for everything, segments give way in a fixed order — profile, then effort, then the whole cwd/branch/dirty group — before the counters are touched; below that, only the brand and the env survive, and below that the header renders nothing.
- The right rail scrolls **Status → Changes → TODO**. Cards cache their rendered content until their own digest or an explicit invalidation changes. `neon` keeps the rounded border and accent title; `float` uses a tone background, a full-height `▎` accent, one-column transparent margins, painted padding above and below, and a blank painted separator between heading and body. Warning/error/success panels retain their tone colors.
- The Status card carries Project (cwd, branch, session name, active profile), Changes, and Integrations (other extensions' statuses); its live digest detects changes without requiring an event. Model, effort, context, cost, and the per-model usage table live in the header instead — the header ticks every frame, so duplicating them in a card would just make that card repaint every frame too.
- The Status card carries Env (the named env, only when launched with `--env`, shown first), Project (cwd, branch, session name, active profile), Changes, and Integrations (other extensions' statuses); its live digest detects changes without requiring an event. Model, effort, context, cost, and the per-model usage table live in the header instead — the header ticks every frame, so duplicating them in a card would just make that card repaint every frame too.
- Gentle Agents is not part of the rail in any mode: its one card stays above the editor, where it already lived, with fixed right-aligned columns for `model · effort`, tokens, cost, and elapsed, each sized to the widest value among the shown tasks — so the numbers line up vertically even when one row's values are much shorter than another's. A queued task fills only the elapsed column with the word `queued`, leaving the other columns blank rather than overwriting the row.
- The sidebar reuses its last frame until something it paints changes, so silent frames stay cheap; a per-section cache means one card's changing digest (or the header's) never forces an unrelated card to redraw.
- Header placement is configurable. Above input, the float header has a full-width background, painted padding above and below its content, and a transparent `▔` bottom edge. Below input, the float footer groups Changes (when the session's roots have uncommitted work), header data, and locally owned integration statuses under one upper edge; it never duplicates statuses owned by the rail.
Expand Down
21 changes: 20 additions & 1 deletion docs/readme-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ An orphan branch with commits and no parent has no branch point to name as `base
```bash
gentle-shell [options] [-- pi-args...]
gentle-shell home [link|isolated|<path>]
gentle-shell envs [list | new <name>]
gentle-shell [home selectors] setup [--dry-run]
gentle-shell upgrade [--channel release|main]
gentle-shell upgrade --rollback
Expand All @@ -314,12 +315,13 @@ gentle-shell self-uninstall [--dry-run] [--yes] [--include-shared]
| `--link` | Home is your own pi home: `GENTLE_SHELL_USER_PI_HOME` when a Gentle Shell session recorded it (so `--link` run from inside an isolated session reaches your original pi, not the isolated home), otherwise `PI_CODING_AGENT_DIR` or `~/.pi/agent`. Reuses your existing pi sign-ins, models, and chats; never writes to its `settings.json`. |
| `--isolated` | Home is `GENTLE_SHELL_HOME` or `~/.gentle-shell/agent`, with its own settings in `~/.gentle-shell/gentle-ai` ([config home](#config-home)); a custom `GENTLE_SHELL_HOME` outside a `.gentle-shell` folder keeps `~/.pi/gentle-ai`. No credential seeding. Default when nothing else is configured. |
| `--home <path>` | Home is the given directory. |
| `--env <name>` | Home is the named env `GENTLE_SHELL_ENVS_DIR/<name>` (default `~/.gentle-shell/envs/<name>`). The env must already exist; an unknown name exits 2 instead of creating a new home. See "Named envs" below. |
| `--package-root <dir>` | Force this directory as the gentle-pi package to load, taking over from any conflicting package the target `settings.json` already declares (see "Loading the package" below). |
| `--help`, `-h` | Print usage (flags, commands, env vars) and exit 0. |
| `--version` | Print `gentle-shell <version>`, `pi <version>`, and `home <mode> <dir>`, then exit 0. |
| `--` | Everything after is forwarded to pi verbatim, even text that looks like a `gentle-shell` flag. |

`--link`, `--isolated`, and `--home` are mutually exclusive; combining two is a usage error, as is `--home` or `--home=` with an empty value. Effective-home precedence: an explicit flag wins, then the persisted `home` subcommand choice, then the `--isolated` default. Every argument gentle-shell does not recognize — `--mode rpc`, `-p "..."`, etc. — is forwarded to pi unchanged.
`--link`, `--isolated`, `--home`, and `--env` are mutually exclusive; combining two is a usage error, as is `--home` or `--home=` with an empty value. Effective-home precedence: an explicit flag wins, then the persisted `home` subcommand choice, then the `--isolated` default. Every argument gentle-shell does not recognize — `--mode rpc`, `-p "..."`, etc. — is forwarded to pi unchanged.

### Config home

Expand Down Expand Up @@ -372,6 +374,23 @@ It never overwrites, never follows a symbolic link, never copies `packages`, `ex

Tradeoff: `--link` shares one credential store with pi; `import-pi` duplicates it. A provider that rotates refresh tokens on use may sign out whichever copy refreshes second; run `/login` again in that home.

### Named envs (`envs` subcommand, `--env`)

A named env is a separate agent home managed by gentle-shell under `~/.gentle-shell/envs/<name>/` (`GENTLE_SHELL_ENVS_DIR` overrides the root). Each env has its own sign-ins, models, packages, settings, sessions, and subagent history. The directory is the registry: there is no separate list. Names are 1-40 lowercase letters, digits, `.`, `_`, or `-`, starting with a letter or digit.

| Command | Effect |
| --- | --- |
| `gentle-shell envs` | On a terminal, opens a full-screen env manager. Without a TTY, it behaves like `envs list`. |
| `gentle-shell envs list` | Prints the base home as `[base]`, then every env, each with its setup state; `(default)` after a name marks what a plain `gentle-shell` opens. |
| `gentle-shell envs new <name>` | Creates the env without launching it. Its first launch installs the companion packages, the same as any new `--home`. |
| `gentle-shell --env <name> [...]` | Launches, sets up (`--env <name> setup`), or runs pi subcommands (`--env <name> install npm:x`) in that env. |

The manager lists the install's own home first, as `Gentle Shell base`. It is the `--isolated` home (`GENTLE_SHELL_HOME` or `~/.gentle-shell/agent`), the one Gentle Shell always used before envs, and nothing about it changes. Opening it is `--isolated`, and making it the default persists `"home": "isolated"`, exactly like `gentle-shell home isolated`. Its sessions carry no env label. It cannot be renamed or deleted from the manager, and an env may still be named `base`. The `(default)` marker, like the `(active)` marker in `/gentle:profiles`, names what a plain launch opens: the base home when no `home` is persisted (or it is `isolated` or malformed), the env a persisted path names, and nothing in the list for `link`.

Manager keys: `↑`/`↓` or `j`/`k` select. `enter` launches the selected env. `n` creates an env, `r` renames it, and `d` deletes it after you type its name. `s` makes it the default. `p` runs `setup`, `i` installs a package source, and `a` runs `pi auth check` for each signed-in provider. `q` or `esc` quits. The detail pane shows the path, the setup record, the session count and last session, the signed-in provider ids (never credential values), and the declared packages.

Inside a session, the header shows `env <name>` right after the brand, and it stays there as the header narrows. The Status card shows an `Env` group with the name, above `Project`. The launcher passes the name to Pi as `GENTLE_SHELL_ENV`, only for a named env, and clears any inherited value for every other home. The default env is stored as the ordinary persisted `home` path, so a plain `gentle-shell` opens it, and `gentle-shell home link|isolated|<path>` replaces it. Renaming or deleting an env keeps `config.json` consistent: its setup record and default follow it or are removed. Envs are separate homes, not sandboxes. They share the repository you work in, `~/.pi/gentle-ai/persona.json`, and `~/.gentle-ai/state.json`, like every other home. Named envs work the same in a bundled (standalone) install. There, the envs root shares `~/.gentle-shell` with the prefix and launches use the bundled Pi. Both `self-uninstall` variants list the envs root once, as "your named envs", under *Not touched*, and never remove it. They also refuse a plan that would remove anything inside it, for example a `GENTLE_SHELL_HOME` that points into an env.

### Managing packages

`gentle-shell install npm:<pkg>`, `gentle-shell remove ...`, `gentle-shell list`, `gentle-shell update ...`, `gentle-shell config`, and `gentle-shell auth ...` run pi's own commands against the resolved home — the `--isolated` home by default, or your own pi home with `--link`. A launcher flag before the subcommand (`--link`, `--isolated`, `--home <path>`) still selects which home the subcommand runs against. Running `gentle-shell install npm:gentle-pi` inside the isolated home is unnecessary: the launcher already loads the Gentle Shell package itself (see "Loading the package" below).
Expand Down
11 changes: 8 additions & 3 deletions extensions/gentle-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ import { installSessionChangeCapture } from "../lib/session-change-capture.ts";
import { SelectionEngine } from "../lib/selection-engine.ts";
import { withOverlayRepaint } from "../lib/overlay-repaint.ts";
import { buildNotificationRows } from "../lib/notification-customize.ts";
import { activeEnvName } from "../lib/gentle-shell-envs.ts";

// Gentle Shell: the visual layer gentle-pi puts on top of pi. It installs the
// status bar, the petal prompt, the working-tree changes widget and overlay,
Expand All @@ -153,6 +154,8 @@ interface ShellBarComponent {
interface BuildOptions {
jobs?: RunningJobDisplay[];
profile?: string;
// Named env from the launcher (GENTLE_SHELL_ENV); omitted for other homes.
env?: string;
home?: string;
dirty?: number;
usage?: ProviderUsage;
Expand Down Expand Up @@ -321,6 +324,7 @@ export function buildShellBarModel(
return {
cwd: shortenHome(ctx.sessionManager.getCwd(), home),
profile: options.profile,
...(options.env ? { env: options.env } : {}),
branch: footerData.getGitBranch(),
dirty: options.dirty,
sessionName: ctx.sessionManager.getSessionName(),
Expand Down Expand Up @@ -355,14 +359,15 @@ export function createShellBarComponent(
dirty: () => number | undefined = () => undefined,
usage: () => ProviderUsage | undefined = () => undefined,
presentation?: () => ReturnType<typeof resolveVisualSettings>["settings"],
env: () => string | undefined = () => undefined,
): ShellBarComponent {
const unsubscribe = footerData.onBranchChange(() => {
host.invalidateSidebar?.();
host.requestRender();
});
return {
render(width: number) {
return renderShellBar(buildShellBarModel(pi, ctx, footerData, { dirty: dirty(), usage: usage() }), theme, width, presentation?.(), gaugePaint(ctx));
return renderShellBar(buildShellBarModel(pi, ctx, footerData, { dirty: dirty(), usage: usage(), env: env() }), theme, width, presentation?.(), gaugePaint(ctx));
},
invalidate() {},
dispose() {
Expand Down Expand Up @@ -1923,13 +1928,13 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
sidebarTui = tui;
if (tui.terminal) sidebarState(tui).visibility = { todo: visualSettings.visibility.todo };
renderHost = { requestRender: () => tui.requestRender(), invalidateSidebar: () => invalidateSidebar(tui) };
const bottom = createShellBarComponent(pi, ctx, renderHost, theme, footerData, () => tracker.model.files.length, () => usage.get(ctx.model?.provider ?? ""), () => visualSettings);
const bottom = createShellBarComponent(pi, ctx, renderHost, theme, footerData, () => tracker.model.files.length, () => usage.get(ctx.model?.provider ?? ""), () => visualSettings, () => activeEnvName(env));
// The Status card paints live session state that no event re-registers a
// part for: model, effort, context, cost, session name and extension
// statuses. The digest is what keeps the fullscreen memo honest, and it
// rebuilds the model exactly as the narrow bottom bar does every frame.
const footerModel = (): ShellBarModel => ({
...buildShellBarModel(pi, ctx, footerData, { dirty: tracker.model.files.length, usage: usage.get(ctx.model?.provider ?? ""), profile: deps.activeProfile(), jobs: runningJobs }),
...buildShellBarModel(pi, ctx, footerData, { dirty: tracker.model.files.length, usage: usage.get(ctx.model?.provider ?? ""), profile: deps.activeProfile(), env: activeEnvName(env), jobs: runningJobs }),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
changes: { files: tracker.model.files.length, added: tracker.model.added, deleted: tracker.model.deleted },
review,
});
Expand Down
23 changes: 2 additions & 21 deletions extensions/startup-banner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { join } from "node:path";
import { resolveAnimationPolicy } from "../lib/animation-policy.ts";
import { PI_SUBCOMMANDS } from "../lib/gentle-shell-launcher.ts";
import { gentlePiConfigHome } from "../lib/agent-home.ts";
import { ROSE_ART as ROSE_LARGE_RAW } from "../lib/gentle-brand.ts";


export type BannerColor = "pink" | "cyan" | "yellow" | "green";
Expand Down Expand Up @@ -42,26 +43,6 @@ const TEXT_LOGO = [
" ▒▒▒▒▒▒",
];

const ROSE_LARGE_RAW = [
" ⣠⣾⣷⣶⣦⣤⣤⣄⣠⣄⣀ ⢀⣀⣀",
" ⢀⣴⣿⣿⠿⣋⣭⣭⣯⣭⣍⣭⣿⣟⠛⠛⠿⣿⣷⣄",
" ⢀⣴⣾⡟⢻⣿⡟⠁⣼⣿⠏⣵⢻⣿⣻⣿⣿⢿⡻⣿⣿⣶⡌⢿⣿⣷⣦⣤⡄",
" ⣤⣶⣾⣿⣿⠏ ⠈⢿⣄ ⢹⣏⠠⠟⣾⣿⣿⣿⣿⣿⠷⣏⣼⠟⢡⣿⡟⠋⢻⣿⣿⡄",
" ⠈⣿⣿⣿⣿⡆ ⣽⢧⡘⠈⠳⣦⣍⠛⠛⢦⣉⣴⣛⣫⣭⣴⡟⠋ ⣾⣿⣿⡿",
" ⢀⠹⣿⣿⣿⣷⣤⡄ ⠋ ⠙⢆ ⣠⠴⠟⠛⣛⣛⣛⠟⠋⠁⠺⡇ ⣀⣴⣿⣿⡟⠁",
" ⠈⣀⠈⠛⠷⠿⣿⣿⣷⣤⣀ ⢠⠋ ⠈⠉⠉ ⣠⣴⣥⠾⠛⠉⣰⣿⣷",
" ⠹⣯⣝⠛⠛⠷⢶⣤⣤⣀ ⢀⡠⠖⠋⠉⢉⣀⣀⣴⣾⣿⠿⠟⠃",
" ⠘⠻⢿⣦⣄⡀ ⠉⠛⢦⠠⢊⠤⠴⢒⣛⣛⣩⣽⡿⠟⠁",
" ⠶⢶⣤⣄⡀⠨⠭⠽⠟⣓⢦⣀⠈⢇⡥⠖⠛⠋⠉⠉",
" ⠈⢷ ⠐⠂⢤⣽⣄ ⠰⡎⠙⠳⣄⡀ ⠈⢣⠘⢦⠋",
" ⠈⢳⣀⡒⠉⠉⣉⠙⡲⣽⣄ ⣏⠳⡄ ⠘⡇ ⡾⠁",
" ⠛⠻⢦⣄⣉⡁⣀⣀⣈⣙⣺⣌⡇⢠⢀⡇⡾",
" ⠈⠉ ⠈⠳⡄⣸⢱⠇",
" ⡷⠡⡯⢖⠉",
" ⢀⡴⢪⠔⣉⠔⠋",
" ⠐⠈",
];

function rgb(r: number, g: number, b: number, text: string): string {
return `\x1b[38;2;${r};${g};${b}m${text}\x1b[39m`;
}
Expand Down Expand Up @@ -665,7 +646,7 @@ export default function (pi: ExtensionAPI) {
// clearing stdout here would leave its previous-frame cache out of sync.
const bannerConfig = await readBannerConfig();
const palette = BANNER_PALETTES[bannerConfig.color];
const roseBase = padLines(normalizeAscii(ROSE_LARGE_RAW));
const roseBase = padLines(normalizeAscii([...ROSE_LARGE_RAW]));
const logoBase = padLines(TEXT_LOGO);
void warmupLetterStrokes();

Expand Down
44 changes: 44 additions & 0 deletions lib/gentle-brand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Gentle's visual identity, shared by the Pi startup banner and the launcher's
// pre-Pi screens (the envs gate). Pure data and ANSI helpers only: the
// launcher runs before any Pi package is loaded.

// The braille rose of the startup banner.
export const ROSE_ART: readonly string[] = Object.freeze([
" ⣠⣾⣷⣶⣦⣤⣤⣄⣠⣄⣀ ⢀⣀⣀",
" ⢀⣴⣿⣿⠿⣋⣭⣭⣯⣭⣍⣭⣿⣟⠛⠛⠿⣿⣷⣄",
" ⢀⣴⣾⡟⢻⣿⡟⠁⣼⣿⠏⣵⢻⣿⣻⣿⣿⢿⡻⣿⣿⣶⡌⢿⣿⣷⣦⣤⡄",
" ⣤⣶⣾⣿⣿⠏ ⠈⢿⣄ ⢹⣏⠠⠟⣾⣿⣿⣿⣿⣿⠷⣏⣼⠟⢡⣿⡟⠋⢻⣿⣿⡄",
" ⠈⣿⣿⣿⣿⡆ ⣽⢧⡘⠈⠳⣦⣍⠛⠛⢦⣉⣴⣛⣫⣭⣴⡟⠋ ⣾⣿⣿⡿",
" ⢀⠹⣿⣿⣿⣷⣤⡄ ⠋ ⠙⢆ ⣠⠴⠟⠛⣛⣛⣛⠟⠋⠁⠺⡇ ⣀⣴⣿⣿⡟⠁",
" ⠈⣀⠈⠛⠷⠿⣿⣿⣷⣤⣀ ⢠⠋ ⠈⠉⠉ ⣠⣴⣥⠾⠛⠉⣰⣿⣷",
" ⠹⣯⣝⠛⠛⠷⢶⣤⣤⣀ ⢀⡠⠖⠋⠉⢉⣀⣀⣴⣾⣿⠿⠟⠃",
" ⠘⠻⢿⣦⣄⡀ ⠉⠛⢦⠠⢊⠤⠴⢒⣛⣛⣩⣽⡿⠟⠁",
" ⠶⢶⣤⣄⡀⠨⠭⠽⠟⣓⢦⣀⠈⢇⡥⠖⠛⠋⠉⠉",
" ⠈⢷ ⠐⠂⢤⣽⣄ ⠰⡎⠙⠳⣄⡀ ⠈⢣⠘⢦⠋",
" ⠈⢳⣀⡒⠉⠉⣉⠙⡲⣽⣄ ⣏⠳⡄ ⠘⡇ ⡾⠁",
" ⠛⠻⢦⣄⣉⡁⣀⣀⣈⣙⣺⣌⡇⢠⢀⡇⡾",
" ⠈⠉ ⠈⠳⡄⣸⢱⠇",
" ⡷⠡⡯⢖⠉",
" ⢀⡴⢪⠔⣉⠔⠋",
" ⠐⠈",
]);

export type Rgb = readonly [number, number, number];

// The banner's default pink palette (startup-banner BANNER_PALETTES.pink) plus
// the Gentle theme's muted and error tones.
export const GENTLE_COLORS = Object.freeze({
rose: [255, 118, 195] as Rgb,
label: [200, 100, 160] as Rgb,
value: [255, 140, 210] as Rgb,
dimRose: [95, 30, 60] as Rgb,
muted: [92, 97, 112] as Rgb,
error: [203, 124, 148] as Rgb,
});

export const BRAND_NAME = "Gentle Shell";
export const BRAND_GLYPH = "✿";

export function truecolor([r, g, b]: Rgb, text: string): string {
return text.length === 0 ? "" : `\x1b[38;2;${r};${g};${b}m${text}\x1b[39m`;
}
Loading