@envpilot/cli 1.4.1 → 1.6.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 +8 -0
- package/README.md +109 -32
- package/dist/app-UJRUWQKL.js +216 -0
- package/dist/chunk-RUYGAIH5.js +4605 -0
- package/dist/index.js +45 -3563
- package/package.json +7 -3
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)
|
|
3
|
+
The official CLI for [Envpilot](https://www.envpilot.dev) -- a secure environment variable management platform for teams.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
29
|
-
envpilot
|
|
28
|
+
# One-command setup: authenticate, select a project, and pull variables
|
|
29
|
+
envpilot sync
|
|
30
30
|
|
|
31
|
-
#
|
|
32
|
-
envpilot
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
envpilot
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
| `envpilot
|
|
48
|
-
| `envpilot
|
|
49
|
-
| `envpilot
|
|
50
|
-
| `envpilot
|
|
51
|
-
| `envpilot
|
|
52
|
-
| `envpilot
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
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
|
-
|
|
139
|
+
## Security
|
|
64
140
|
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
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
|
|
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
|
-
|
|
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-RUYGAIH5.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
|
+
};
|