Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Secure environment secrets management using native OS credential stores.
- Export secrets as shell environment variables (`eval $(envsec env)`)
- Load secrets from `.env` files (with conflict detection)
- Share secrets encrypted with GPG for team members
- Interactive terminal UI (`envsec tui`) for managing secrets without memorizing commands

## Packages

Expand All @@ -31,6 +32,7 @@ This is a monorepo containing the following packages:
| [`envsec`](./packages/cli) | CLI tool for managing secrets | [![npm](https://img.shields.io/npm/v/envsec)](https://www.npmjs.com/package/envsec) |
| [`@envsec/sdk`](./packages/sdk) | Node.js / Bun SDK for loading secrets programmatically | [![npm](https://img.shields.io/npm/v/@envsec/sdk)](https://www.npmjs.com/package/@envsec/sdk) |
| [`@envsec/core`](./packages/core) | Core engine — OS credential store adapters + metadata DB | [![npm](https://img.shields.io/npm/v/@envsec/core)](https://www.npmjs.com/package/@envsec/core) |
| [`@envsec/tui`](./packages/tui) | Interactive terminal UI for secrets management | [![npm](https://img.shields.io/npm/v/@envsec/tui)](https://www.npmjs.com/package/@envsec/tui) |

## SDK Quick Start

Expand Down Expand Up @@ -383,6 +385,44 @@ Secrets with an `--expires` duration set via `envsec add` are tracked in metadat

The `audit` command also tracks generated `.env` files. Every time `env-file` is used, the output path, context, and timestamp are recorded. The audit output includes a second section listing these files. If a tracked `.env` file no longer exists on disk, audit automatically removes it from the metadata and reports the cleanup.

### Interactive TUI

envsec includes a full-screen terminal UI for managing secrets interactively — no need to memorize commands.

```bash
# Launch the TUI
envsec tui

# Launch with a pre-selected context
envsec -c myapp.dev tui
```

The TUI provides eight screens accessible from the main menu:

- **Contexts** — browse all contexts, set active context with `s`, clear context with `x`, view secret counts, delete entire contexts
- **Secrets** — list secrets in a table, reveal values, add or delete secrets
- **Add Secret** — interactive form with masked input and optional expiry duration
- **Search** — glob pattern search across secrets or contexts
- **Saved Commands** — list, view, and delete saved command templates
- **Audit** — check for expired/expiring secrets, review tracked `.env` file exports
- **Import .env** — load secrets from a `.env` file into the current context
- **Export .env** — export secrets to a `.env` file (tracked for audit)

Keyboard shortcuts:

| Key | Action |
|-----|--------|
| `↑` / `↓` | Navigate menu items and table rows |
| `Enter` | Select / confirm |
| `c` | Open contexts view (main menu) |
| `s` | Set selected as active context (contexts view) |
| `x` | Clear active context (contexts view) |
| `a` | Add a new secret (secrets view) |
| `d` | Delete selected item |
| `r` | Reveal secret value (detail view) |
| `Esc` | Go back / cancel |
| `q` | Quit the TUI |

### Diagnose your setup

```bash
Expand Down Expand Up @@ -496,6 +536,7 @@ packages/
cli/ → envsec CLI (published as `envsec`)
sdk/ → Node.js/Bun SDK (published as `@envsec/sdk`)
core/ → Core engine, shared by CLI and SDK (published as `@envsec/core`)
tui/ → Interactive terminal UI (published as `@envsec/tui`)
apps/
website/ → Documentation website
```
Expand Down
164 changes: 164 additions & 0 deletions apps/website/components/docs-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,170 @@ envsec --json doctor`}
</P>
</Section>

{/* Interactive TUI */}
<Section id="tui-overview">
<H2>Interactive TUI</H2>
<P>
envsec includes a full-screen terminal UI for managing secrets without
memorizing commands. Launch it with <Mono>envsec tui</Mono> or
optionally pass a context to start in.
</P>
<CodeBlock
code={`# Launch the TUI
envsec tui

# Launch with a pre-selected context
envsec -c myapp.dev tui`}
/>
<P>
The TUI uses raw ANSI escape sequences with zero external
dependencies. It runs in an alternate screen buffer so your terminal
history stays clean.
</P>
</Section>

<Section id="tui-views">
<H2>Views &amp; Screens</H2>
<P>
The main menu provides access to eight screens, each covering a core
envsec workflow.
</P>
<H3>Contexts</H3>
<P>
Browse all contexts with their secret counts. Press <Mono>s</Mono> to
set the selected context as the active context for the session,{" "}
<Mono>x</Mono> to clear the active context, <Mono>Enter</Mono> to view
its secrets, or <Mono>d</Mono> to delete all secrets in a context
(with confirmation).
</P>
<H3>Secrets</H3>
<P>
Lists all secrets in the current context as a table with key, last
updated, and expiry columns. Press <Mono>Enter</Mono> to reveal a
secret value, <Mono>a</Mono> to add a new secret, or <Mono>d</Mono> to
delete the selected secret.
</P>
<H3>Add Secret</H3>
<P>
Interactive form to store a new secret. Prompts for key, value (masked
input), and an optional expiry duration (e.g. <Mono>30d</Mono>,{" "}
<Mono>1y</Mono>, <Mono>6mo</Mono>).
</P>
<H3>Search</H3>
<P>
Glob pattern search. With a context selected, searches secret keys.
Without a context, searches context names.
</P>
<H3>Saved Commands</H3>
<P>
Lists all saved commands in a table with name, command template, and
context. Press <Mono>d</Mono> to delete a command.
</P>
<H3>Audit</H3>
<P>
Scans for secrets expiring within 30 days. Shows expired vs. expiring
status with time distance. Also lists tracked <Mono>.env</Mono> file
exports and cleans up stale records for files that no longer exist on
disk.
</P>
<H3>Import .env</H3>
<P>
Prompts for a file path (defaults to <Mono>.env</Mono>) and imports
all key-value pairs into the current context. Keys are converted from{" "}
<Mono>UPPER_SNAKE_CASE</Mono> to <Mono>dotted.lowercase</Mono>.
</P>
<H3>Export .env</H3>
<P>
Prompts for an output path (defaults to <Mono>.env</Mono>) and writes
all secrets from the current context. The export is tracked in
metadata for the audit view.
</P>
</Section>

<Section id="tui-keyboard">
<H2>Keyboard Shortcuts</H2>
<div className="mb-6 overflow-x-auto">
<table className="w-full text-left text-sm">
<thead>
<tr className="border-white/10 border-b text-muted-foreground">
<th className="py-2 pr-4 font-medium">Key</th>
<th className="py-2 font-medium">Action</th>
</tr>
</thead>
<tbody className="text-muted-foreground">
<tr className="border-white/5 border-b">
<td className="py-2 pr-4">
<Mono>↑ / ↓</Mono>
</td>
<td className="py-2">Navigate menu items and table rows</td>
</tr>
<tr className="border-white/5 border-b">
<td className="py-2 pr-4">
<Mono>Enter</Mono>
</td>
<td className="py-2">Select / confirm</td>
</tr>
<tr className="border-white/5 border-b">
<td className="py-2 pr-4">
<Mono>c</Mono>
</td>
<td className="py-2">Open contexts view (main menu)</td>
</tr>
<tr className="border-white/5 border-b">
<td className="py-2 pr-4">
<Mono>s</Mono>
</td>
<td className="py-2">
Set selected as active context (contexts view)
</td>
</tr>
<tr className="border-white/5 border-b">
<td className="py-2 pr-4">
<Mono>x</Mono>
</td>
<td className="py-2">Clear active context (contexts view)</td>
</tr>
<tr className="border-white/5 border-b">
<td className="py-2 pr-4">
<Mono>a</Mono>
</td>
<td className="py-2">Add a new secret (secrets view)</td>
</tr>
<tr className="border-white/5 border-b">
<td className="py-2 pr-4">
<Mono>d</Mono>
</td>
<td className="py-2">Delete selected item</td>
</tr>
<tr className="border-white/5 border-b">
<td className="py-2 pr-4">
<Mono>r</Mono>
</td>
<td className="py-2">Reveal secret value (detail view)</td>
</tr>
<tr className="border-white/5 border-b">
<td className="py-2 pr-4">
<Mono>Esc</Mono>
</td>
<td className="py-2">Go back / cancel</td>
</tr>
<tr className="border-white/5 border-b">
<td className="py-2 pr-4">
<Mono>q</Mono>
</td>
<td className="py-2">Quit the TUI</td>
</tr>
<tr className="border-white/5 border-b">
<td className="py-2 pr-4">
<Mono>Ctrl+C</Mono>
</td>
<td className="py-2">Quit the TUI</td>
</tr>
</tbody>
</table>
</div>
</Section>

{/* Configuration */}
<Section id="contexts">
<H2>Contexts</H2>
Expand Down
9 changes: 9 additions & 0 deletions apps/website/components/docs-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ const SECTIONS = [
{ id: "doctor", label: "doctor" },
],
},
{
title: "Interactive TUI",
items: [
{ id: "tui-overview", label: "Overview" },
{ id: "tui-views", label: "Views & Screens" },
{ id: "tui-keyboard", label: "Keyboard Shortcuts" },
],
},
{
title: "Configuration",
items: [
Expand Down Expand Up @@ -66,6 +74,7 @@ export function DocsSidebar() {
const [expanded, setExpanded] = useState<Record<string, boolean>>({
"Getting Started": true,
Commands: true,
"Interactive TUI": true,
Configuration: true,
SDK: true,
Security: true,
Expand Down
6 changes: 6 additions & 0 deletions apps/website/components/features.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ const FEATURES = [
description:
"Tab completions for bash, zsh, fish, and PowerShell. Feels native in every shell.",
},
{
icon: Monitor,
title: "Interactive TUI",
description:
"Full-screen terminal UI for browsing contexts, managing secrets, running audits, and importing/exporting — all without memorizing commands.",
},
] as const;

export function Features() {
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
},
"dependencies": {
"@envsec/core": "workspace:*",
"@envsec/tui": "workspace:*",
"@effect/cli": "^0.75.0",
"@effect/platform": "^0.96.0",
"@effect/platform-node": "^0.106.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/cli-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { rootCommand } from "./cli/root.js";
import { runCommand } from "./cli/run.js";
import { searchCommand } from "./cli/search.js";
import { shareCommand } from "./cli/share.js";
import { tuiCommand } from "./cli/tui.js";
import { generateCompletions, type ShellType } from "./completions/index.js";

const require = createRequire(import.meta.url);
Expand All @@ -48,6 +49,7 @@ const command = rootCommand.pipe(
envCommand,
loadCommand,
shareCommand,
tuiCommand,
auditCommand,
doctorCommand,
])
Expand Down
12 changes: 12 additions & 0 deletions packages/cli/src/cli/tui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Command } from "@effect/cli";
import { runTUI } from "@envsec/tui";
import { Effect, Option } from "effect";
import { optionalContext } from "./root.js";

export const tuiCommand = Command.make("tui", {}, () =>
Effect.gen(function* () {
const context = yield* optionalContext;
const ctx = Option.isSome(context) ? context.value : null;
yield* runTUI(ctx);
})
);
54 changes: 54 additions & 0 deletions packages/tui/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "@envsec/tui",
"version": "1.0.0-beta.11",
"description": "Interactive terminal UI for envsec secrets management",
"keywords": [
"tui",
"terminal",
"interactive",
"secrets",
"envsec"
],
"license": "MIT",
"author": "David Nussio",
"repository": {
"type": "git",
"url": "git+https://github.com/davidnussio/envsec.git",
"directory": "packages/tui"
},
"homepage": "https://envsec.dev",
"bugs": {
"url": "https://github.com/davidnussio/envsec/issues"
},
"publishConfig": {
"access": "public"
},
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsc",
"lint": "tsc --noEmit",
"clean": "rm -rf dist"
},
"dependencies": {
"@envsec/core": "workspace:*",
"effect": "^3.21.0"
},
"devDependencies": {
"@types/node": "^25.5.0",
"typescript": "^5.9.3"
},
"engines": {
"node": ">=22"
}
}
Loading
Loading