@envpilot/cli 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ Copyright (c) 2024-present Envpilot. All rights reserved.
2
+
3
+ This software is proprietary and confidential. No part of this software may be
4
+ reproduced, distributed, or transmitted in any form or by any means without the
5
+ prior written permission of Envpilot.
6
+
7
+ For licensing inquiries, contact hello@envpilot.dev.
8
+ See https://www.envpilot.dev/terms for the full Terms of Service.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @envpilot/cli
2
2
 
3
- The official CLI for [Envpilot](https://www.envpilot.dev) a secure environment variable management platform for teams.
3
+ The official CLI for [Envpilot](https://www.envpilot.dev) -- a secure environment variable management platform for teams.
4
4
 
5
- Envpilot lets you sync, share, and manage `.env` files across your team without leaking secrets in Slack, email, or Git. Variables are encrypted at rest using [WorkOS Vault](https://workos.com/vault) and access is controlled through role-based permissions.
5
+ Sync, share, and manage `.env` files across your team without leaking secrets in Slack, email, or Git. Variables are encrypted at rest using [WorkOS Vault](https://workos.com/vault) and access is enforced through role-based permissions.
6
6
 
7
7
  ## Install
8
8
 
@@ -10,7 +10,7 @@ Envpilot lets you sync, share, and manage `.env` files across your team without
10
10
  npm install -g @envpilot/cli
11
11
  ```
12
12
 
13
- Or with bun:
13
+ With bun:
14
14
 
15
15
  ```bash
16
16
  bun install -g @envpilot/cli
@@ -25,46 +25,123 @@ npx @envpilot/cli login
25
25
  ## Quick Start
26
26
 
27
27
  ```bash
28
- # Authenticate with your Envpilot account
29
- envpilot login
28
+ # One-command setup: authenticate, select a project, and pull variables
29
+ envpilot sync
30
30
 
31
- # Initialize a project in the current directory
32
- envpilot init
31
+ # Or step by step:
32
+ envpilot login # Authenticate with your Envpilot account
33
+ envpilot init # Link the current directory to a project
34
+ envpilot pull # Pull variables into a local .env file
35
+ ```
36
+
37
+ ## Interactive Terminal UI
33
38
 
34
- # Pull environment variables into a .env file
35
- envpilot pull
39
+ Run `envpilot` with no arguments to open the interactive TUI. Browse commands, read documentation, and launch any command directly from the keyboard-driven dashboard.
36
40
 
37
- # Push local .env changes to Envpilot
38
- envpilot push
41
+ ```bash
42
+ envpilot
39
43
  ```
40
44
 
45
+ The TUI returns after each command finishes, so you can run multiple commands in one session. Press **Esc** to exit.
46
+
41
47
  ## Commands
42
48
 
43
- | Command | Description |
44
- | ------------------------- | --------------------------------------------------- |
45
- | `envpilot login` | Authenticate with your Envpilot account |
46
- | `envpilot logout` | Log out and clear stored credentials |
47
- | `envpilot init` | Link the current directory to an Envpilot project |
48
- | `envpilot pull` | Pull environment variables into a local `.env` file |
49
- | `envpilot push` | Push local `.env` changes to Envpilot |
50
- | `envpilot list orgs` | List your organizations |
51
- | `envpilot list projects` | List projects in the active organization |
52
- | `envpilot list variables` | List variables in the active project |
53
- | `envpilot switch` | Switch the active project |
54
- | `envpilot config` | View or update CLI configuration |
55
- | `envpilot whoami` | Show the currently authenticated user |
49
+ ### Getting Started
50
+
51
+ | Command | Description |
52
+ | -------------------------- | ------------------------------------------------------- |
53
+ | `envpilot` | Open the interactive terminal UI |
54
+ | `envpilot ui` | Open the interactive terminal UI (explicit) |
55
+ | `envpilot login [options]` | Authenticate with your Envpilot account |
56
+ | `envpilot sync [options]` | Authenticate, select a project, and pull in one flow |
57
+ | `envpilot init [options]` | Link the current directory to a project and environment |
58
+ | `envpilot man [command]` | Show the CLI manual page |
59
+
60
+ ### Syncing Variables
61
+
62
+ | Command | Description |
63
+ | ------------------------- | --------------------------------------- |
64
+ | `envpilot pull [options]` | Pull variables into a local `.env` file |
65
+ | `envpilot push [options]` | Push local `.env` changes to Envpilot |
66
+
67
+ ### Browsing Resources
68
+
69
+ | Command | Description |
70
+ | ----------------------------- | -------------------------------------------------- |
71
+ | `envpilot list` | List projects in the active organization (default) |
72
+ | `envpilot list organizations` | List your organizations |
73
+ | `envpilot list projects` | List projects in the active organization |
74
+ | `envpilot list variables` | List variables in the active project |
75
+ | `envpilot list linked` | List projects linked in the current directory |
76
+ | `envpilot usage [options]` | Show plan usage and feature limits |
77
+
78
+ ### Project Management
79
+
80
+ | Command | Description |
81
+ | --------------------------- | ------------------------------------------------------- |
82
+ | `envpilot switch [options]` | Switch the active organization, project, or environment |
83
+ | `envpilot unlink [project]` | Remove a linked project from the current directory |
84
+
85
+ ### Account
86
+
87
+ | Command | Description |
88
+ | ------------------------------ | ----------------------------------------------- |
89
+ | `envpilot whoami` | Show the authenticated user and active context |
90
+ | `envpilot config [subcommand]` | View or update CLI configuration |
91
+ | `envpilot logout` | Revoke the session and clear stored credentials |
92
+
93
+ Run `envpilot man` for the full command reference, or `envpilot man <command>` for detailed help on a specific command.
94
+
95
+ ## Common Options
96
+
97
+ | Flag | Applies to | Description |
98
+ | ----------------------- | ---------------------------------------------- | -------------------------------------------------------------------- |
99
+ | `--api-url <url>` | `login` | Override the API URL |
100
+ | `--no-browser` | `login` | Skip automatic browser open |
101
+ | `--organization <id>` | `sync`, `init`, `switch` | Target a specific organization |
102
+ | `--project <id>` | `sync`, `init`, `switch` | Target a specific project |
103
+ | `--env <environment>` | `sync`, `init`, `pull`, `push`, `switch` | Target an environment (development, staging, production) |
104
+ | `--file <path>` | `pull`, `push` | Custom file path (default: `.env`) |
105
+ | `--format <format>` | `pull` | Output format: env, json, yaml, vercel, netlify, aws, docker-compose |
106
+ | `--merge` / `--replace` | `push` | Merge with or replace remote variables |
107
+ | `--dry-run` | `pull` | Preview changes without writing to disk |
108
+ | `--json` | `list projects`, `list organizations`, `usage` | Output as JSON |
109
+ | `--force` | `unlink` | Skip confirmation prompt |
110
+
111
+ ## Pull Formats
112
+
113
+ `envpilot pull` supports multiple export formats for integration with different platforms:
114
+
115
+ ```bash
116
+ envpilot pull # .env (default)
117
+ envpilot pull --format json # JSON
118
+ envpilot pull --format yaml # YAML
119
+ envpilot pull --format vercel # Vercel
120
+ envpilot pull --format netlify # Netlify
121
+ envpilot pull --format aws # AWS Parameter Store
122
+ envpilot pull --format docker-compose # Docker Compose
123
+ ```
56
124
 
57
125
  ## Role-Based Access
58
126
 
59
127
  Envpilot enforces two-tier role-based access control:
60
128
 
61
- **Organization roles**: Admin, Team Lead, Member
129
+ **Organization roles** -- Admin, Team Lead, Member
130
+
131
+ **Project roles** -- Manager, Developer, Viewer
132
+
133
+ | Role | Pull | Push (direct) | Push (approval request) | Manage permissions |
134
+ | --------- | ------------------------ | ------------- | ----------------------- | ------------------ |
135
+ | Manager | Yes | Yes | -- | Yes |
136
+ | Developer | Yes | No | Yes | No |
137
+ | Viewer | Permitted variables only | No | No | No |
62
138
 
63
- **Project roles**: Manager, Developer, Viewer
139
+ ## Security
64
140
 
65
- - **Viewers** have read-only access to explicitly permitted variables
66
- - **Developers** can push changes, which creates pending approval requests
67
- - **Managers** can push directly and approve pending requests
141
+ - Variables are encrypted at rest using [WorkOS Vault](https://workos.com/vault).
142
+ - The CLI never stores plaintext secret values on disk.
143
+ - Authentication uses a device-code flow with short-lived access tokens and refresh tokens.
144
+ - All API communication is over HTTPS.
68
145
 
69
146
  ## Requirements
70
147
 
@@ -74,10 +151,10 @@ Envpilot enforces two-tier role-based access control:
74
151
  ## Links
75
152
 
76
153
  - [Website](https://www.envpilot.dev)
154
+ - [Documentation](https://www.envpilot.dev/docs)
77
155
  - [Privacy Policy](https://www.envpilot.dev/privacy)
78
- - [Terms & Conditions](https://www.envpilot.dev/terms)
79
- - [GitHub](https://github.com/rafay99-epic/envpilot.dev)
156
+ - [Terms of Service](https://www.envpilot.dev/terms)
80
157
 
81
158
  ## License
82
159
 
83
- This software is proprietary. See [Terms & Conditions](https://www.envpilot.dev/terms) for details.
160
+ Proprietary. See [Terms of Service](https://www.envpilot.dev/terms) for details.
@@ -0,0 +1,216 @@
1
+ import {
2
+ formatArgv,
3
+ getApiUrl,
4
+ getTopLevelCommandCatalog,
5
+ getUser,
6
+ isAuthenticated
7
+ } from "./chunk-CIIQSYAS.js";
8
+
9
+ // src/ui/app.tsx
10
+ import {
11
+ startTransition,
12
+ useDeferredValue,
13
+ useEffect,
14
+ useMemo,
15
+ useState
16
+ } from "react";
17
+ import { Box, Text, useApp, useInput } from "ink";
18
+ import { jsx, jsxs } from "react/jsx-runtime";
19
+ var searchIndex = /* @__PURE__ */ new WeakMap();
20
+ function getSearchableText(command) {
21
+ let text = searchIndex.get(command);
22
+ if (text === void 0) {
23
+ const parts = [
24
+ command.title,
25
+ command.description,
26
+ formatArgv(command.argv),
27
+ command.args ?? "",
28
+ command.category
29
+ ];
30
+ if (command.aliases) {
31
+ for (const alias of command.aliases) parts.push(formatArgv(alias));
32
+ }
33
+ for (const example of command.examples) parts.push(formatArgv(example));
34
+ for (const note of command.notes) parts.push(note);
35
+ for (const kw of command.keywords) parts.push(kw);
36
+ text = parts.filter(Boolean).join(" ").toLowerCase();
37
+ searchIndex.set(command, text);
38
+ }
39
+ return text;
40
+ }
41
+ function matchesQuery(command, query) {
42
+ if (!query.trim()) {
43
+ return true;
44
+ }
45
+ return getSearchableText(command).includes(query.trim().toLowerCase());
46
+ }
47
+ var TOP_LEVEL_CATALOG = getTopLevelCommandCatalog();
48
+ function useCommandSearch() {
49
+ const [query, setQuery] = useState("");
50
+ const deferredQuery = useDeferredValue(query);
51
+ const commands = useMemo(
52
+ () => TOP_LEVEL_CATALOG.filter(
53
+ (command) => matchesQuery(command, deferredQuery)
54
+ ),
55
+ [deferredQuery]
56
+ );
57
+ return {
58
+ query,
59
+ commands,
60
+ updateQuery(nextValue) {
61
+ startTransition(() => {
62
+ setQuery(nextValue);
63
+ });
64
+ }
65
+ };
66
+ }
67
+ function Section({
68
+ title,
69
+ children
70
+ }) {
71
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
72
+ /* @__PURE__ */ jsx(Text, { color: "greenBright", children: title }),
73
+ children
74
+ ] });
75
+ }
76
+ function CLIApp({ onSelectCommand }) {
77
+ const { exit } = useApp();
78
+ const { query, commands, updateQuery } = useCommandSearch();
79
+ const [selectedIndex, setSelectedIndex] = useState(0);
80
+ const [isRunning, setIsRunning] = useState(false);
81
+ useEffect(() => {
82
+ if (selectedIndex >= commands.length) {
83
+ setSelectedIndex(Math.max(commands.length - 1, 0));
84
+ }
85
+ }, [commands.length, selectedIndex]);
86
+ const selectedCommand = commands[selectedIndex] ?? TOP_LEVEL_CATALOG[0];
87
+ const user = getUser();
88
+ useInput((input, key) => {
89
+ if (isRunning) {
90
+ return;
91
+ }
92
+ if (key.escape) {
93
+ exit();
94
+ return;
95
+ }
96
+ if (key.return && selectedCommand) {
97
+ setIsRunning(true);
98
+ onSelectCommand?.(selectedCommand.argv);
99
+ exit();
100
+ return;
101
+ }
102
+ if (key.upArrow) {
103
+ setSelectedIndex(
104
+ (current) => current === 0 ? commands.length - 1 : current - 1
105
+ );
106
+ return;
107
+ }
108
+ if (key.downArrow) {
109
+ setSelectedIndex(
110
+ (current) => current >= commands.length - 1 ? 0 : current + 1
111
+ );
112
+ return;
113
+ }
114
+ if (key.backspace || key.delete) {
115
+ updateQuery(query.slice(0, -1));
116
+ return;
117
+ }
118
+ if (key.ctrl && input === "u") {
119
+ updateQuery("");
120
+ return;
121
+ }
122
+ if (!key.ctrl && !key.meta && input) {
123
+ updateQuery(query + input);
124
+ }
125
+ });
126
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
127
+ /* @__PURE__ */ jsxs(
128
+ Box,
129
+ {
130
+ borderStyle: "round",
131
+ borderColor: "green",
132
+ paddingX: 1,
133
+ paddingY: 1,
134
+ flexDirection: "column",
135
+ children: [
136
+ /* @__PURE__ */ jsx(Text, { color: "greenBright", children: "Envpilot CLI" }),
137
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Website-aligned terminal UI for auth, projects, variables, and usage." }),
138
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
139
+ /* @__PURE__ */ jsxs(Text, { children: [
140
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "API " }),
141
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: getApiUrl() })
142
+ ] }),
143
+ /* @__PURE__ */ jsxs(Text, { children: [
144
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Auth " }),
145
+ /* @__PURE__ */ jsx(Text, { color: isAuthenticated() ? "green" : "yellow", children: isAuthenticated() ? `Signed in${user?.email ? ` as ${user.email}` : ""}` : "Not signed in" })
146
+ ] })
147
+ ] })
148
+ ]
149
+ }
150
+ ),
151
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
152
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Search " }),
153
+ /* @__PURE__ */ jsx(Text, { color: "white", children: query || "type to filter commands" })
154
+ ] }),
155
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, children: [
156
+ /* @__PURE__ */ jsxs(
157
+ Box,
158
+ {
159
+ width: "45%",
160
+ borderStyle: "round",
161
+ borderColor: "gray",
162
+ flexDirection: "column",
163
+ paddingX: 1,
164
+ paddingY: 1,
165
+ marginRight: 1,
166
+ children: [
167
+ /* @__PURE__ */ jsx(Text, { color: "greenBright", children: "Commands" }),
168
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u2191/\u2193 move, Enter run, Esc exit, Ctrl+U clear" }),
169
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: commands.length === 0 ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: "No commands match the current search." }) : commands.map((command, index) => {
170
+ const active = index === selectedIndex;
171
+ return /* @__PURE__ */ jsxs(Box, { children: [
172
+ /* @__PURE__ */ jsx(Text, { color: active ? "black" : "gray", children: active ? "\u203A " : " " }),
173
+ /* @__PURE__ */ jsx(
174
+ Text,
175
+ {
176
+ backgroundColor: active ? "green" : void 0,
177
+ color: active ? "black" : "white",
178
+ children: formatArgv(command.argv)
179
+ }
180
+ )
181
+ ] }, command.id);
182
+ }) })
183
+ ]
184
+ }
185
+ ),
186
+ /* @__PURE__ */ jsxs(
187
+ Box,
188
+ {
189
+ width: "55%",
190
+ borderStyle: "round",
191
+ borderColor: "gray",
192
+ flexDirection: "column",
193
+ paddingX: 1,
194
+ paddingY: 1,
195
+ children: [
196
+ /* @__PURE__ */ jsx(Text, { color: "greenBright", children: selectedCommand.title }),
197
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: selectedCommand.category }),
198
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "column", children: [
199
+ /* @__PURE__ */ jsx(Text, { children: selectedCommand.description }),
200
+ selectedCommand.args ? /* @__PURE__ */ jsx(Text, { color: "gray", children: selectedCommand.args }) : null
201
+ ] }),
202
+ /* @__PURE__ */ jsx(Section, { title: "Website alignment", children: /* @__PURE__ */ jsx(Text, { children: selectedCommand.websiteSurface }) }),
203
+ /* @__PURE__ */ jsx(Section, { title: "Examples", children: selectedCommand.examples.map((example) => /* @__PURE__ */ jsx(Text, { color: "cyan", children: formatArgv(example) }, formatArgv(example))) }),
204
+ /* @__PURE__ */ jsx(Section, { title: "Notes", children: selectedCommand.notes.map((note) => /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
205
+ "\u2022 ",
206
+ note
207
+ ] }, note)) })
208
+ ]
209
+ }
210
+ )
211
+ ] })
212
+ ] });
213
+ }
214
+ export {
215
+ CLIApp
216
+ };