@etree/cli 2.0.1 → 2.0.2

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/dist/index.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@etree/cli",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "private": false,
5
+ "files": [
6
+ "dist"
7
+ ],
5
8
  "bin": {
6
- "et": "./dist/index.js",
7
- "envtree": "./dist/index.js"
9
+ "et": "dist/index.js",
10
+ "envtree": "dist/index.js"
8
11
  },
9
12
  "scripts": {
10
13
  "dev": "tsc --watch",
package/eslint.config.mjs DELETED
@@ -1,4 +0,0 @@
1
- import { config } from "@envtree/eslint-config/base";
2
-
3
- /** @type {import("eslint").Linter.Config[]} */
4
- export default config;
@@ -1,258 +0,0 @@
1
- import { Command } from "commander";
2
- import inquirer from "inquirer";
3
- import chalk from "chalk";
4
- import ora from "ora";
5
- import { apiRequest } from "../lib/api";
6
- import { saveSession, clearSession, getSession } from "../lib/config";
7
- import { generateKeys, derivePublicKey, shutdown } from "../lib/crypto";
8
- import { savePrivateKey, hasPrivateKey } from "../lib/key-store";
9
-
10
- export function registerAuthCommands(program: Command): void {
11
- // ── signup ──
12
- program
13
- .command("signup")
14
- .description("Create a new EnvTree account")
15
- .action(async () => {
16
- const answers = await inquirer.prompt([
17
- { type: "input", name: "email", message: "Email:" },
18
- { type: "input", name: "username", message: "Username:" },
19
- {
20
- type: "password",
21
- name: "password",
22
- message: "Password:",
23
- mask: "*",
24
- },
25
- {
26
- type: "password",
27
- name: "confirmPassword",
28
- message: "Confirm Password:",
29
- mask: "*",
30
- },
31
- ]);
32
-
33
- if (answers.password !== answers.confirmPassword) {
34
- console.log(chalk.red("Passwords do not match"));
35
- return;
36
- }
37
-
38
- const spinner = ora("Creating account...").start();
39
- try {
40
- const result = await apiRequest(
41
- "POST",
42
- "/auth/signup",
43
- {
44
- email: answers.email,
45
- password: answers.password,
46
- username: answers.username,
47
- },
48
- false,
49
- );
50
-
51
- if (!result.session) {
52
- spinner.succeed(
53
- "Account created! Please verify your email, then run: et login",
54
- );
55
- return;
56
- }
57
-
58
- saveSession({
59
- access_token: result.session.access_token,
60
- refresh_token: result.session.refresh_token,
61
- user_id: result.user.id,
62
- email: answers.email,
63
- username: answers.username,
64
- });
65
-
66
- spinner.text = "Generating encryption keys...";
67
- const keys = await generateKeys();
68
- savePrivateKey(keys.private_key);
69
-
70
- await apiRequest("PUT", "/profile/public-key", {
71
- public_key: keys.public_key,
72
- });
73
-
74
- spinner.succeed(chalk.green("Account created & keys generated!"));
75
- console.log(
76
- chalk.dim(` User: ${answers.username} (${answers.email})`),
77
- );
78
- console.log(
79
- chalk.dim(" Private key saved to ~/.envtree/private_key"),
80
- );
81
- } catch (err: any) {
82
- spinner.fail(chalk.red(`Signup failed: ${err.message}`));
83
- } finally {
84
- shutdown();
85
- }
86
- });
87
-
88
- // ── login ──
89
- program
90
- .command("login")
91
- .description("Log in to your EnvTree account")
92
- .option("-e, --email <email>", "Email (non-interactive)")
93
- .option("-p, --password <password>", "Password (non-interactive)")
94
- .action(async (opts: { email?: string; password?: string }) => {
95
- let email = opts.email;
96
- let password = opts.password;
97
-
98
- // Interactive mode if flags not provided
99
- if (!email || !password) {
100
- const answers = await inquirer.prompt([
101
- ...(!email
102
- ? [{ type: "input", name: "email", message: "Email:" }]
103
- : []),
104
- ...(!password
105
- ? [
106
- {
107
- type: "password",
108
- name: "password",
109
- message: "Password:",
110
- mask: "*",
111
- },
112
- ]
113
- : []),
114
- ]);
115
- email = email || answers.email;
116
- password = password || answers.password;
117
- }
118
-
119
- const spinner = ora("Logging in...").start();
120
- try {
121
- const result = await apiRequest(
122
- "POST",
123
- "/auth/login",
124
- { email, password },
125
- false,
126
- );
127
-
128
- saveSession({
129
- access_token: result.session.access_token,
130
- refresh_token: result.session.refresh_token,
131
- user_id: result.user.id,
132
- email: result.user.email,
133
- username: email!,
134
- });
135
-
136
- // Fetch profile to get real username
137
- const profileResult = await apiRequest(
138
- "GET",
139
- "/profile/me",
140
- undefined,
141
- true,
142
- );
143
- if (profileResult.profile?.username) {
144
- saveSession({
145
- access_token: result.session.access_token,
146
- refresh_token: result.session.refresh_token,
147
- user_id: result.user.id,
148
- email: result.user.email,
149
- username: profileResult.profile.username,
150
- });
151
- }
152
-
153
- // Always sync public key with server
154
- if (hasPrivateKey()) {
155
- // Derive public key from existing private key and upload it
156
- spinner.text = "Syncing encryption keys...";
157
- const privateKey = require("../lib/key-store").getPrivateKey();
158
- const publicKey = await derivePublicKey(privateKey);
159
- await apiRequest("PUT", "/profile/public-key", {
160
- public_key: publicKey,
161
- });
162
- spinner.succeed(chalk.green("Logged in!"));
163
- } else {
164
- // Generate a new keypair
165
- spinner.text = "Generating encryption keys...";
166
- const keys = await generateKeys();
167
- savePrivateKey(keys.private_key);
168
-
169
- await apiRequest("PUT", "/profile/public-key", {
170
- public_key: keys.public_key,
171
- });
172
-
173
- spinner.succeed(chalk.green("Logged in & keys generated!"));
174
- console.log(
175
- chalk.dim(" Private key saved to ~/.envtree/private_key"),
176
- );
177
- }
178
-
179
- console.log(chalk.dim(` User: ${result.user.email}`));
180
- } catch (err: any) {
181
- spinner.fail(chalk.red(`Login failed: ${err.message}`));
182
- } finally {
183
- shutdown();
184
- }
185
- });
186
-
187
- // ── logout ──
188
- program
189
- .command("logout")
190
- .description("Log out of your EnvTree account")
191
- .action(() => {
192
- clearSession();
193
- console.log(chalk.green("Logged out"));
194
- });
195
-
196
- // ── whoami ──
197
- program
198
- .command("whoami")
199
- .description("Show the current logged-in user")
200
- .action(async () => {
201
- const session = getSession();
202
- if (!session) {
203
- console.log(chalk.yellow("Not logged in. Run: et login"));
204
- return;
205
- }
206
-
207
- try {
208
- const result = await apiRequest("GET", "/profile/me");
209
- console.log(chalk.bold("EnvTree User"));
210
- console.log(` Username: ${result.profile.username}`);
211
- console.log(` Email: ${result.profile.email}`);
212
- console.log(
213
- ` Public Key: ${result.profile.public_key ? result.profile.public_key.substring(0, 20) + "..." : chalk.yellow("not set")}`,
214
- );
215
- } catch {
216
- console.log(chalk.dim(` Username: ${session.username}`));
217
- console.log(chalk.dim(` Email: ${session.email}`));
218
- console.log(chalk.yellow(" (offline — showing cached info)"));
219
- }
220
- });
221
-
222
- // ── delete-account ──
223
- program
224
- .command("delete-account")
225
- .description("Permanently delete your EnvTree account")
226
- .action(async () => {
227
- const session = getSession();
228
- if (!session) {
229
- console.log(chalk.yellow("Not logged in. Run: et login"));
230
- return;
231
- }
232
-
233
- const { confirm } = await inquirer.prompt([
234
- {
235
- type: "confirm",
236
- name: "confirm",
237
- message: chalk.red(
238
- "⚠ This will permanently delete your account and all your wallets. Are you sure?",
239
- ),
240
- default: false,
241
- },
242
- ]);
243
-
244
- if (!confirm) {
245
- console.log(chalk.dim("Cancelled."));
246
- return;
247
- }
248
-
249
- const spinner = ora("Deleting account...").start();
250
- try {
251
- await apiRequest("DELETE", "/auth/account");
252
- clearSession();
253
- spinner.succeed(chalk.green("Account deleted. Goodbye!"));
254
- } catch (err: any) {
255
- spinner.fail(chalk.red(`Failed: ${err.message}`));
256
- }
257
- });
258
- }
@@ -1,47 +0,0 @@
1
- import { Command } from "commander";
2
- import chalk from "chalk";
3
- import { getConfig, saveConfig } from "../lib/config";
4
-
5
- export function registerConfigCommands(program: Command): void {
6
- const config = program
7
- .command("config")
8
- .description("Manage CLI configuration");
9
-
10
- // ── config show ──
11
- config
12
- .command("show")
13
- .description("Display current configuration")
14
- .action(() => {
15
- const cfg = getConfig();
16
- console.log(chalk.bold("\nEnvTree Config"));
17
- console.log(` Server: ${chalk.cyan(cfg.server_url)}`);
18
- console.log(
19
- ` Session: ${cfg.session ? chalk.green("active") + chalk.dim(` (${cfg.session.email})`) : chalk.yellow("none")}`,
20
- );
21
- console.log(
22
- chalk.dim(` Config: ~/.envtree/config.json`),
23
- );
24
- console.log(
25
- chalk.dim(` Keys: ~/.envtree/private_key`),
26
- );
27
- console.log("");
28
- });
29
-
30
- // ── config set ──
31
- config
32
- .command("set <key> <value>")
33
- .description("Set a configuration value (e.g. server URL)")
34
- .action((key: string, value: string) => {
35
- const cfg = getConfig();
36
-
37
- if (key === "server" || key === "server_url") {
38
- cfg.server_url = value;
39
- saveConfig(cfg);
40
- console.log(chalk.green(`Server URL set to: ${value}`));
41
- } else {
42
- console.log(
43
- chalk.red(`Unknown config key: "${key}". Available: server`),
44
- );
45
- }
46
- });
47
- }
@@ -1,187 +0,0 @@
1
- import { Command } from "commander";
2
- import inquirer from "inquirer";
3
- import chalk from "chalk";
4
- import ora from "ora";
5
- import { apiRequest } from "../lib/api";
6
- import { saveSession } from "../lib/config";
7
- import { generateKeys, derivePublicKey, shutdown } from "../lib/crypto";
8
- import { savePrivateKey, hasPrivateKey, getPrivateKey } from "../lib/key-store";
9
-
10
- export function registerInitCommand(program: Command): void {
11
- program
12
- .command("init")
13
- .description("First-time setup — create an account or log in")
14
- .action(async () => {
15
- console.log(chalk.bold("\n🌲 Welcome to EnvTree!\n"));
16
-
17
- const { action } = await inquirer.prompt([
18
- {
19
- type: "list",
20
- name: "action",
21
- message: "What would you like to do?",
22
- choices: [
23
- { name: "Create a new account", value: "signup" },
24
- { name: "Log in to existing account", value: "login" },
25
- ],
26
- },
27
- ]);
28
-
29
- if (action === "signup") {
30
- const answers = await inquirer.prompt([
31
- { type: "input", name: "email", message: "Email:" },
32
- { type: "input", name: "username", message: "Username:" },
33
- {
34
- type: "password",
35
- name: "password",
36
- message: "Password:",
37
- mask: "*",
38
- },
39
- {
40
- type: "password",
41
- name: "confirmPassword",
42
- message: "Confirm Password:",
43
- mask: "*",
44
- },
45
- ]);
46
-
47
- if (answers.password !== answers.confirmPassword) {
48
- console.log(chalk.red("\n Passwords do not match"));
49
- return;
50
- }
51
-
52
- const spinner = ora("Creating account...").start();
53
- try {
54
- const result = await apiRequest(
55
- "POST",
56
- "/auth/signup",
57
- {
58
- email: answers.email,
59
- password: answers.password,
60
- username: answers.username,
61
- },
62
- false,
63
- );
64
-
65
- if (!result.session) {
66
- spinner.succeed(
67
- "Account created! Check your email, then run: et login",
68
- );
69
- return;
70
- }
71
-
72
- saveSession({
73
- access_token: result.session.access_token,
74
- refresh_token: result.session.refresh_token,
75
- user_id: result.user.id,
76
- email: answers.email,
77
- username: answers.username,
78
- });
79
-
80
- spinner.text = "Generating encryption keys...";
81
- const keys = await generateKeys();
82
- savePrivateKey(keys.private_key);
83
-
84
- await apiRequest("PUT", "/profile/public-key", {
85
- public_key: keys.public_key,
86
- });
87
-
88
- spinner.succeed(chalk.green("All set! You're ready to go."));
89
- console.log(
90
- chalk.dim(` User: ${answers.username} (${answers.email})`),
91
- );
92
- console.log("");
93
- console.log(chalk.bold(" Next steps:"));
94
- console.log(
95
- chalk.cyan(" et wallet create my-project ") +
96
- chalk.dim("Create a wallet"),
97
- );
98
- console.log(
99
- chalk.cyan(' et push my-project API_KEY "sk-..." ') +
100
- chalk.dim("Store a secret"),
101
- );
102
- console.log(
103
- chalk.cyan(" et pull my-project ") +
104
- chalk.dim("Sync to .env"),
105
- );
106
- console.log("");
107
- } catch (err: any) {
108
- spinner.fail(chalk.red(`Signup failed: ${err.message}`));
109
- } finally {
110
- shutdown();
111
- }
112
- } else {
113
- // Login flow
114
- const answers = await inquirer.prompt([
115
- { type: "input", name: "email", message: "Email:" },
116
- {
117
- type: "password",
118
- name: "password",
119
- message: "Password:",
120
- mask: "*",
121
- },
122
- ]);
123
-
124
- const spinner = ora("Logging in...").start();
125
- try {
126
- const result = await apiRequest(
127
- "POST",
128
- "/auth/login",
129
- {
130
- email: answers.email,
131
- password: answers.password,
132
- },
133
- false,
134
- );
135
-
136
- saveSession({
137
- access_token: result.session.access_token,
138
- refresh_token: result.session.refresh_token,
139
- user_id: result.user.id,
140
- email: result.user.email,
141
- username: answers.email,
142
- });
143
-
144
- const profileResult = await apiRequest(
145
- "GET",
146
- "/profile/me",
147
- undefined,
148
- true,
149
- );
150
- if (profileResult.profile?.username) {
151
- saveSession({
152
- access_token: result.session.access_token,
153
- refresh_token: result.session.refresh_token,
154
- user_id: result.user.id,
155
- email: result.user.email,
156
- username: profileResult.profile.username,
157
- });
158
- }
159
-
160
- // Always sync public key with server
161
- if (hasPrivateKey()) {
162
- spinner.text = "Syncing encryption keys...";
163
- const pk = getPrivateKey()!;
164
- const publicKey = await derivePublicKey(pk);
165
- await apiRequest("PUT", "/profile/public-key", {
166
- public_key: publicKey,
167
- });
168
- } else {
169
- spinner.text = "Generating encryption keys...";
170
- const keys = await generateKeys();
171
- savePrivateKey(keys.private_key);
172
- await apiRequest("PUT", "/profile/public-key", {
173
- public_key: keys.public_key,
174
- });
175
- }
176
-
177
- spinner.succeed(chalk.green("Logged in! You're ready to go."));
178
- console.log(chalk.dim(` User: ${result.user.email}`));
179
- console.log("");
180
- } catch (err: any) {
181
- spinner.fail(chalk.red(`Login failed: ${err.message}`));
182
- } finally {
183
- shutdown();
184
- }
185
- }
186
- });
187
- }
@@ -1,146 +0,0 @@
1
- import { Command } from "commander";
2
- import chalk from "chalk";
3
- import ora from "ora";
4
- import { api } from "../lib/api";
5
-
6
- /**
7
- * Helper: find a wallet ID by name from the user's wallets.
8
- */
9
- async function findWalletId(name: string): Promise<string | null> {
10
- const result = await api.get("/wallets");
11
- const owned = result.owned.find((w: any) => w.name === name);
12
- if (owned) return owned.id;
13
- const shared = result.shared.find((w: any) => w.name === name);
14
- if (shared) return shared.id;
15
- return null;
16
- }
17
-
18
- export function registerMemberCommands(program: Command): void {
19
- const member = program.command("member").description("Manage wallet members");
20
-
21
- // ── member add ──
22
- member
23
- .command("add <wallet> <username>")
24
- .description("Add a member to a wallet")
25
- .option("-r, --role <role>", "Role: admin, read, write", "read")
26
- .action(
27
- async (walletName: string, username: string, opts: { role: string }) => {
28
- const spinner = ora("Adding member...").start();
29
- try {
30
- const walletId = await findWalletId(walletName);
31
- if (!walletId) {
32
- spinner.fail(chalk.red(`Wallet "${walletName}" not found`));
33
- return;
34
- }
35
-
36
- const result = await api.post(`/wallets/${walletId}/members`, {
37
- username,
38
- role: opts.role,
39
- });
40
-
41
- spinner.succeed(
42
- chalk.green(
43
- `Added "${username}" as ${opts.role} to "${walletName}"`,
44
- ),
45
- );
46
-
47
- if (result.public_key) {
48
- console.log(
49
- chalk.dim(
50
- ` Their public key: ${result.public_key.substring(0, 20)}...`,
51
- ),
52
- );
53
- console.log(
54
- chalk.yellow(
55
- " ⚠ Remember to re-encrypt secrets for this user:",
56
- ),
57
- );
58
- console.log(
59
- chalk.yellow(` envtree secret share ${walletName}`),
60
- );
61
- }
62
- } catch (err: any) {
63
- spinner.fail(chalk.red(`Failed: ${err.message}`));
64
- }
65
- },
66
- );
67
-
68
- // ── member list ──
69
- member
70
- .command("list <wallet>")
71
- .alias("ls")
72
- .description("List members of a wallet")
73
- .action(async (walletName: string) => {
74
- const spinner = ora("Fetching members...").start();
75
- try {
76
- const walletId = await findWalletId(walletName);
77
- if (!walletId) {
78
- spinner.fail(chalk.red(`Wallet "${walletName}" not found`));
79
- return;
80
- }
81
-
82
- const result = await api.get(`/wallets/${walletId}/members`);
83
- spinner.stop();
84
-
85
- console.log(chalk.bold(`\nMembers of "${walletName}"`));
86
-
87
- // Owner
88
- if (result.owner) {
89
- console.log(
90
- ` ${chalk.cyan(result.owner.username)} ${chalk.dim(`(${result.owner.email})`)} ${chalk.green("[owner]")}`,
91
- );
92
- }
93
-
94
- // Members
95
- if (result.members && result.members.length > 0) {
96
- for (const m of result.members) {
97
- const roleColor =
98
- m.role === "admin"
99
- ? chalk.magenta
100
- : m.role === "write"
101
- ? chalk.blue
102
- : chalk.dim;
103
- console.log(
104
- ` ${chalk.cyan(m.user.username)} ${chalk.dim(`(${m.user.email})`)} ${roleColor(`[${m.role}]`)}`,
105
- );
106
- }
107
- } else {
108
- console.log(chalk.dim(" No additional members"));
109
- }
110
- console.log("");
111
- } catch (err: any) {
112
- spinner.fail(chalk.red(`Failed: ${err.message}`));
113
- }
114
- });
115
-
116
- // ── member remove ──
117
- member
118
- .command("remove <wallet> <username>")
119
- .description("Remove a member from a wallet")
120
- .action(async (walletName: string, username: string) => {
121
- const spinner = ora("Removing member...").start();
122
- try {
123
- const walletId = await findWalletId(walletName);
124
- if (!walletId) {
125
- spinner.fail(chalk.red(`Wallet "${walletName}" not found`));
126
- return;
127
- }
128
-
129
- // Look up user ID by username
130
- const profileResult = await api.get(`/profile/${username}`);
131
- if (!profileResult.profile) {
132
- spinner.fail(chalk.red(`User "${username}" not found`));
133
- return;
134
- }
135
-
136
- await api.delete(
137
- `/wallets/${walletId}/members/${profileResult.profile.id}`,
138
- );
139
- spinner.succeed(
140
- chalk.green(`Removed "${username}" from "${walletName}"`),
141
- );
142
- } catch (err: any) {
143
- spinner.fail(chalk.red(`Failed: ${err.message}`));
144
- }
145
- });
146
- }