@etree/cli 2.0.1 → 2.0.3
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/lib/crypto.d.ts +0 -15
- package/dist/lib/crypto.js +28 -152
- package/dist/lib/crypto.js.map +1 -1
- package/package.json +8 -3
- package/eslint.config.mjs +0 -4
- package/src/commands/auth.ts +0 -258
- package/src/commands/config.ts +0 -47
- package/src/commands/init.ts +0 -187
- package/src/commands/member.ts +0 -146
- package/src/commands/secret.ts +0 -381
- package/src/commands/shortcuts.ts +0 -25
- package/src/commands/wallet.ts +0 -97
- package/src/index.ts +0 -50
- package/src/lib/api.ts +0 -57
- package/src/lib/config.ts +0 -70
- package/src/lib/crypto.ts +0 -173
- package/src/lib/env-manager.ts +0 -60
- package/src/lib/key-store.ts +0 -51
- package/src/test-e2e.ts +0 -106
- package/tsconfig.json +0 -18
package/dist/lib/crypto.d.ts
CHANGED
|
@@ -1,23 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generate a new X25519 key pair.
|
|
3
|
-
*/
|
|
4
1
|
export declare function generateKeys(): Promise<{
|
|
5
2
|
private_key: string;
|
|
6
3
|
public_key: string;
|
|
7
4
|
}>;
|
|
8
|
-
/**
|
|
9
|
-
* Derive the public key from an existing private key.
|
|
10
|
-
*/
|
|
11
5
|
export declare function derivePublicKey(privateKey: string): Promise<string>;
|
|
12
|
-
/**
|
|
13
|
-
* Encrypt a plaintext value using the recipient's public key (SealedBox).
|
|
14
|
-
*/
|
|
15
6
|
export declare function encrypt(plaintext: string, publicKey: string): Promise<string>;
|
|
16
|
-
/**
|
|
17
|
-
* Decrypt a ciphertext using the recipient's private key (SealedBox).
|
|
18
|
-
*/
|
|
19
7
|
export declare function decrypt(ciphertext: string, privateKey: string): Promise<string>;
|
|
20
|
-
/**
|
|
21
|
-
* Shut down the crypto engine process.
|
|
22
|
-
*/
|
|
23
8
|
export declare function shutdown(): void;
|
package/dist/lib/crypto.js
CHANGED
|
@@ -1,173 +1,49 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
35
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
6
|
exports.generateKeys = generateKeys;
|
|
37
7
|
exports.derivePublicKey = derivePublicKey;
|
|
38
8
|
exports.encrypt = encrypt;
|
|
39
9
|
exports.decrypt = decrypt;
|
|
40
10
|
exports.shutdown = shutdown;
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* Get or spawn the crypto engine Python process.
|
|
47
|
-
*/
|
|
48
|
-
function getEngine() {
|
|
49
|
-
if (engineProcess && !engineProcess.killed) {
|
|
50
|
-
return engineProcess;
|
|
51
|
-
}
|
|
52
|
-
// Try venv python first, fall back to system python
|
|
53
|
-
const venvDir = path.resolve(__dirname, "../../../../packages/crypto/.venv");
|
|
54
|
-
const isWindows = process.platform === "win32";
|
|
55
|
-
// Potential Venv Paths
|
|
56
|
-
const potentialVenvPaths = isWindows
|
|
57
|
-
? [
|
|
58
|
-
path.join(venvDir, "Scripts", "python.exe"),
|
|
59
|
-
path.join(venvDir, "bin", "python.exe"),
|
|
60
|
-
]
|
|
61
|
-
: [path.join(venvDir, "bin", "python")];
|
|
62
|
-
let pythonCmd = isWindows ? "python" : "python3";
|
|
63
|
-
for (const venvPath of potentialVenvPaths) {
|
|
64
|
-
if (require("fs").existsSync(venvPath)) {
|
|
65
|
-
pythonCmd = venvPath;
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
// If we haven't found a venv python, verify the system command exists
|
|
70
|
-
if (pythonCmd === "python" || pythonCmd === "python3") {
|
|
71
|
-
try {
|
|
72
|
-
// Use system python if it exists and works
|
|
73
|
-
require("child_process").execSync(`${pythonCmd} --version`, {
|
|
74
|
-
stdio: "ignore",
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
catch {
|
|
78
|
-
// If the preferred one fails, try the fallback
|
|
79
|
-
pythonCmd = pythonCmd === "python3" ? "python" : "python3";
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
engineProcess = (0, child_process_1.spawn)(pythonCmd, [CRYPTO_ENGINE_PATH], {
|
|
83
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
84
|
-
cwd: path.resolve(__dirname, "../../../../packages/crypto"),
|
|
85
|
-
});
|
|
86
|
-
engineProcess.on("exit", () => {
|
|
87
|
-
engineProcess = null;
|
|
88
|
-
});
|
|
89
|
-
return engineProcess;
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Send a command to the crypto engine and get the response.
|
|
93
|
-
*/
|
|
94
|
-
function sendCommand(request) {
|
|
95
|
-
return new Promise((resolve, reject) => {
|
|
96
|
-
const engine = getEngine();
|
|
97
|
-
const onData = (data) => {
|
|
98
|
-
try {
|
|
99
|
-
const response = JSON.parse(data.toString().trim());
|
|
100
|
-
engine.stdout.removeListener("data", onData);
|
|
101
|
-
engine.stderr.removeListener("data", onError);
|
|
102
|
-
if (response.status === "error") {
|
|
103
|
-
reject(new Error(response.message));
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
resolve(response);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
catch (e) {
|
|
110
|
-
// partial data, wait for more
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
const onError = (data) => {
|
|
114
|
-
engine.stdout.removeListener("data", onData);
|
|
115
|
-
engine.stderr.removeListener("data", onError);
|
|
116
|
-
reject(new Error(`Crypto engine error: ${data.toString()}`));
|
|
117
|
-
};
|
|
118
|
-
engine.stdout.on("data", onData);
|
|
119
|
-
engine.stderr.on("data", onError);
|
|
120
|
-
engine.stdin.write(JSON.stringify(request) + "\n");
|
|
121
|
-
});
|
|
11
|
+
const libsodium_wrappers_1 = __importDefault(require("libsodium-wrappers"));
|
|
12
|
+
const buffer_1 = require("buffer");
|
|
13
|
+
async function getSodium() {
|
|
14
|
+
await libsodium_wrappers_1.default.ready;
|
|
15
|
+
return libsodium_wrappers_1.default;
|
|
122
16
|
}
|
|
123
|
-
/**
|
|
124
|
-
* Generate a new X25519 key pair.
|
|
125
|
-
*/
|
|
126
17
|
async function generateKeys() {
|
|
127
|
-
const
|
|
18
|
+
const sodium = await getSodium();
|
|
19
|
+
const keypair = sodium.crypto_box_keypair();
|
|
128
20
|
return {
|
|
129
|
-
private_key:
|
|
130
|
-
public_key:
|
|
21
|
+
private_key: buffer_1.Buffer.from(keypair.privateKey).toString("base64"),
|
|
22
|
+
public_key: buffer_1.Buffer.from(keypair.publicKey).toString("base64"),
|
|
131
23
|
};
|
|
132
24
|
}
|
|
133
|
-
/**
|
|
134
|
-
* Derive the public key from an existing private key.
|
|
135
|
-
*/
|
|
136
25
|
async function derivePublicKey(privateKey) {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return result.public_key;
|
|
26
|
+
const sodium = await getSodium();
|
|
27
|
+
const sk = buffer_1.Buffer.from(privateKey, "base64");
|
|
28
|
+
const pk = sodium.crypto_scalarmult_base(sk);
|
|
29
|
+
return buffer_1.Buffer.from(pk).toString("base64");
|
|
142
30
|
}
|
|
143
|
-
/**
|
|
144
|
-
* Encrypt a plaintext value using the recipient's public key (SealedBox).
|
|
145
|
-
*/
|
|
146
31
|
async function encrypt(plaintext, publicKey) {
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return result.ciphertext;
|
|
32
|
+
const sodium = await getSodium();
|
|
33
|
+
const pk = buffer_1.Buffer.from(publicKey, "base64");
|
|
34
|
+
const encrypted = sodium.crypto_box_seal(sodium.from_string(plaintext), pk);
|
|
35
|
+
return buffer_1.Buffer.from(encrypted).toString("base64");
|
|
152
36
|
}
|
|
153
|
-
/**
|
|
154
|
-
* Decrypt a ciphertext using the recipient's private key (SealedBox).
|
|
155
|
-
*/
|
|
156
37
|
async function decrypt(ciphertext, privateKey) {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
38
|
+
const sodium = await getSodium();
|
|
39
|
+
const ct = buffer_1.Buffer.from(ciphertext, "base64");
|
|
40
|
+
const sk = buffer_1.Buffer.from(privateKey, "base64");
|
|
41
|
+
// Need to compute public key corresponding to private key for unsealing
|
|
42
|
+
const pk = sodium.crypto_scalarmult_base(sk);
|
|
43
|
+
const decrypted = sodium.crypto_box_seal_open(ct, pk, sk);
|
|
44
|
+
return sodium.to_string(decrypted);
|
|
162
45
|
}
|
|
163
|
-
/**
|
|
164
|
-
* Shut down the crypto engine process.
|
|
165
|
-
*/
|
|
166
46
|
function shutdown() {
|
|
167
|
-
|
|
168
|
-
engineProcess.stdin.end();
|
|
169
|
-
engineProcess.kill();
|
|
170
|
-
engineProcess = null;
|
|
171
|
-
}
|
|
47
|
+
// Graceful stub for backward compatibility
|
|
172
48
|
}
|
|
173
49
|
//# sourceMappingURL=crypto.js.map
|
package/dist/lib/crypto.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/lib/crypto.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/lib/crypto.ts"],"names":[],"mappings":";;;;;AAQA,oCAUC;AAED,0CAKC;AAED,0BAQC;AAED,0BAaC;AAED,4BAEC;AAtDD,4EAAyC;AACzC,mCAAgC;AAEhC,KAAK,UAAU,SAAS;IACtB,MAAM,4BAAO,CAAC,KAAK,CAAC;IACpB,OAAO,4BAAO,CAAC;AACjB,CAAC;AAEM,KAAK,UAAU,YAAY;IAIhC,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,MAAM,CAAC,kBAAkB,EAAE,CAAC;IAC5C,OAAO;QACL,WAAW,EAAE,eAAM,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC/D,UAAU,EAAE,eAAM,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;KAC9D,CAAC;AACJ,CAAC;AAEM,KAAK,UAAU,eAAe,CAAC,UAAkB;IACtD,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAC;IACjC,MAAM,EAAE,GAAG,eAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC7C,MAAM,EAAE,GAAG,MAAM,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC;IAC7C,OAAO,eAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC5C,CAAC;AAEM,KAAK,UAAU,OAAO,CAC3B,SAAiB,EACjB,SAAiB;IAEjB,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAC;IACjC,MAAM,EAAE,GAAG,eAAM,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC;IAC5E,OAAO,eAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACnD,CAAC;AAEM,KAAK,UAAU,OAAO,CAC3B,UAAkB,EAClB,UAAkB;IAElB,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAC;IACjC,MAAM,EAAE,GAAG,eAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC7C,MAAM,EAAE,GAAG,eAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAE7C,wEAAwE;IACxE,MAAM,EAAE,GAAG,MAAM,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC;IAE7C,MAAM,SAAS,GAAG,MAAM,CAAC,oBAAoB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;IAC1D,OAAO,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;AACrC,CAAC;AAED,SAAgB,QAAQ;IACtB,2CAA2C;AAC7C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etree/cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"private": false,
|
|
5
|
+
"files": [
|
|
6
|
+
"dist"
|
|
7
|
+
],
|
|
5
8
|
"bin": {
|
|
6
|
-
"et": "
|
|
7
|
-
"envtree": "
|
|
9
|
+
"et": "dist/index.js",
|
|
10
|
+
"envtree": "dist/index.js"
|
|
8
11
|
},
|
|
9
12
|
"scripts": {
|
|
10
13
|
"dev": "tsc --watch",
|
|
@@ -17,12 +20,14 @@
|
|
|
17
20
|
"commander": "^13.1.0",
|
|
18
21
|
"conf": "^10.2.0",
|
|
19
22
|
"inquirer": "^8.2.6",
|
|
23
|
+
"libsodium-wrappers": "^0.8.2",
|
|
20
24
|
"ora": "^5.4.1"
|
|
21
25
|
},
|
|
22
26
|
"devDependencies": {
|
|
23
27
|
"@envtree/eslint-config": "workspace:*",
|
|
24
28
|
"@envtree/typescript-config": "workspace:*",
|
|
25
29
|
"@types/inquirer": "^8.2.10",
|
|
30
|
+
"@types/libsodium-wrappers": "^0.8.2",
|
|
26
31
|
"@types/node": "^22.15.2",
|
|
27
32
|
"tsx": "^4.19.4",
|
|
28
33
|
"typescript": "5.9.2"
|
package/eslint.config.mjs
DELETED
package/src/commands/auth.ts
DELETED
|
@@ -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
|
-
}
|
package/src/commands/config.ts
DELETED
|
@@ -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
|
-
}
|