@elding/cli 0.2.0 → 0.3.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.
@@ -0,0 +1 @@
1
+ export declare function doctor(): Promise<void>;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.doctor = doctor;
4
+ const config_js_1 = require("../lib/config.js");
5
+ const api_js_1 = require("../lib/api.js");
6
+ async function doctor() {
7
+ const { default: chalk } = await import("chalk");
8
+ const ok = (m) => console.log(`${chalk.green("✓")} ${m}`);
9
+ const warn = (m) => console.log(`${chalk.yellow("!")} ${m}`);
10
+ const fail = (m) => console.log(`${chalk.red("✗")} ${m}`);
11
+ console.log(chalk.dim(`API : ${api_js_1.BASE_URL}\n`));
12
+ // 1. Token local
13
+ const config = (0, config_js_1.readConfig)();
14
+ if (!config) {
15
+ fail("Non connecté — lancez `elding login`");
16
+ return;
17
+ }
18
+ ok("Token local présent (~/.elding/config.json)");
19
+ // 2. Token valide + utilisateur
20
+ let accessToken;
21
+ try {
22
+ accessToken = await (0, api_js_1.exchangeToken)(config.refreshToken);
23
+ const me = await (0, api_js_1.getMe)(accessToken);
24
+ ok(`Authentifié : ${me.email}`);
25
+ }
26
+ catch {
27
+ fail("Token expiré ou révoqué — relancez `elding login`");
28
+ return;
29
+ }
30
+ // 3. Set du projet
31
+ const project = (0, config_js_1.readProject)();
32
+ if (!project) {
33
+ warn("Aucun set configuré — lancez `elding init` ou `elding use <set>`");
34
+ return;
35
+ }
36
+ ok(`Set actif : ${project.setName}`);
37
+ // 4. Clés + verrouillage host
38
+ try {
39
+ const keys = await (0, api_js_1.listKeys)(accessToken, project.setId);
40
+ if (keys.length === 0)
41
+ warn("Le set ne contient aucune clé");
42
+ else {
43
+ const locked = keys.filter((k) => k.allowedHost).length;
44
+ ok(`${keys.length} clé(s) — ${locked} verrouillée(s) sur un domaine`);
45
+ if (locked < keys.length)
46
+ warn(`${keys.length - locked} clé(s) sans domaine : exfiltrables via le proxy`);
47
+ }
48
+ }
49
+ catch {
50
+ fail("Impossible de lire les clés du set");
51
+ }
52
+ // 5. Proxy
53
+ if (process.env.ELDING_PROXY_URL)
54
+ ok(`Proxy actif : ${process.env.ELDING_PROXY_URL}`);
55
+ else
56
+ console.log(chalk.dim("○ Proxy inactif (normal hors `elding proxy`)"));
57
+ }
@@ -0,0 +1 @@
1
+ export declare function keys(): Promise<void>;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.keys = keys;
4
+ const api_js_1 = require("../lib/api.js");
5
+ const config_js_1 = require("../lib/config.js");
6
+ const session_js_1 = require("../lib/session.js");
7
+ async function keys() {
8
+ const { default: chalk } = await import("chalk");
9
+ const { default: ora } = await import("ora");
10
+ const project = (0, config_js_1.readProject)();
11
+ if (!project) {
12
+ console.error(chalk.red("Projet non initialisé. Lancez `elding init` ou `elding use <set>`."));
13
+ process.exit(1);
14
+ }
15
+ const spinner = ora("Récupération des clés...").start();
16
+ const accessToken = await (0, session_js_1.requireAccessToken)();
17
+ const list = await (0, api_js_1.listKeys)(accessToken, project.setId);
18
+ spinner.stop();
19
+ console.log(chalk.dim(`Set : ${project.setName}`));
20
+ if (list.length === 0) {
21
+ console.log(chalk.yellow("Aucune clé dans ce set."));
22
+ return;
23
+ }
24
+ for (const k of list) {
25
+ const host = k.allowedHost
26
+ ? chalk.dim(` → ${k.allowedHost}`)
27
+ : chalk.yellow(" → aucun domaine (non verrouillé)");
28
+ console.log(` ${k.name}${host}`);
29
+ }
30
+ }
@@ -0,0 +1 @@
1
+ export declare function open(): Promise<void>;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.open = open;
4
+ const api_js_1 = require("../lib/api.js");
5
+ async function open() {
6
+ const { default: chalk } = await import("chalk");
7
+ const { default: openUrl } = await import("open");
8
+ const url = `${api_js_1.BASE_URL}/vault`;
9
+ await openUrl(url).catch(() => {
10
+ console.log(chalk.cyan(`Ouvrez manuellement : ${url}`));
11
+ });
12
+ console.log(chalk.dim(`Vault ouvert : ${url}`));
13
+ }
@@ -1 +1 @@
1
- export declare function proxy(cmd: string, args: string[]): Promise<void>;
1
+ export declare function proxy(cmd: string, args: string[], verbose?: boolean): Promise<void>;
@@ -5,7 +5,7 @@ const child_process_1 = require("child_process");
5
5
  const config_js_1 = require("../lib/config.js");
6
6
  const api_js_1 = require("../lib/api.js");
7
7
  const proxyServer_js_1 = require("../lib/proxyServer.js");
8
- async function proxy(cmd, args) {
8
+ async function proxy(cmd, args, verbose = false) {
9
9
  const { default: chalk } = await import("chalk");
10
10
  const { default: ora } = await import("ora");
11
11
  const config = (0, config_js_1.readConfig)();
@@ -29,7 +29,7 @@ async function proxy(cmd, args) {
29
29
  spinner.fail(chalk.red(err instanceof Error ? err.message : "Erreur inconnue"));
30
30
  process.exit(1);
31
31
  }
32
- const server = await (0, proxyServer_js_1.startProxy)(secrets, hosts);
32
+ const server = await (0, proxyServer_js_1.startProxy)(secrets, hosts, verbose);
33
33
  spinner.succeed(chalk.green(`Proxy actif sur ${server.url} — ${Object.keys(secrets).length} secret(s)`));
34
34
  console.log(chalk.dim("Les clés restent dans le proxy, jamais dans la mémoire de l'app."));
35
35
  const child = (0, child_process_1.spawn)(cmd, args, {
@@ -21,7 +21,7 @@ async function run(cmd, args) {
21
21
  let secrets;
22
22
  try {
23
23
  const accessToken = await (0, api_js_1.exchangeToken)(config.refreshToken);
24
- ({ secrets } = await (0, api_js_1.fetchSecrets)(accessToken, project.setId));
24
+ ({ secrets } = await (0, api_js_1.fetchSecrets)(accessToken, project.setId, "run"));
25
25
  spinner.succeed(chalk.green(`${Object.keys(secrets).length} secret(s) chargé(s).`));
26
26
  }
27
27
  catch (err) {
@@ -0,0 +1 @@
1
+ export declare function sets(): Promise<void>;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sets = sets;
4
+ const api_js_1 = require("../lib/api.js");
5
+ const config_js_1 = require("../lib/config.js");
6
+ const session_js_1 = require("../lib/session.js");
7
+ async function sets() {
8
+ const { default: chalk } = await import("chalk");
9
+ const { default: ora } = await import("ora");
10
+ const spinner = ora("Récupération des sets...").start();
11
+ const accessToken = await (0, session_js_1.requireAccessToken)();
12
+ const list = await (0, api_js_1.listSets)(accessToken);
13
+ spinner.stop();
14
+ if (list.length === 0) {
15
+ console.log(chalk.yellow("Aucun set. Créez-en un sur le vault."));
16
+ return;
17
+ }
18
+ const activeId = (0, config_js_1.readProject)()?.setId;
19
+ for (const s of list) {
20
+ const marker = s.id === activeId ? chalk.green("●") : chalk.dim("○");
21
+ const name = s.id === activeId ? chalk.green(s.name) : s.name;
22
+ console.log(`${marker} ${name} ${chalk.dim(`(${s.id})`)}`);
23
+ }
24
+ }
@@ -0,0 +1 @@
1
+ export declare function use(nameArg: string): Promise<void>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.use = use;
4
+ const api_js_1 = require("../lib/api.js");
5
+ const config_js_1 = require("../lib/config.js");
6
+ const session_js_1 = require("../lib/session.js");
7
+ async function use(nameArg) {
8
+ const { default: chalk } = await import("chalk");
9
+ const { default: ora } = await import("ora");
10
+ const spinner = ora("Récupération des sets...").start();
11
+ const accessToken = await (0, session_js_1.requireAccessToken)();
12
+ const list = await (0, api_js_1.listSets)(accessToken);
13
+ spinner.stop();
14
+ const query = nameArg.trim().toLowerCase();
15
+ const matches = list.filter((s) => s.name.toLowerCase() === query);
16
+ const candidates = matches.length ? matches : list.filter((s) => s.name.toLowerCase().includes(query));
17
+ if (candidates.length === 0) {
18
+ console.error(chalk.red(`Aucun set nommé "${nameArg}".`) + chalk.dim(" Voir `elding sets`."));
19
+ process.exit(1);
20
+ }
21
+ if (candidates.length > 1) {
22
+ console.error(chalk.red(`Plusieurs sets correspondent à "${nameArg}" :`));
23
+ candidates.forEach((s) => console.error(chalk.dim(` - ${s.name}`)));
24
+ process.exit(1);
25
+ }
26
+ const set = candidates[0];
27
+ (0, config_js_1.writeProject)({ setId: set.id, setName: set.name });
28
+ console.log(chalk.green(`✓ Set actif : "${set.name}"`));
29
+ }
@@ -0,0 +1 @@
1
+ export declare function whoami(): Promise<void>;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.whoami = whoami;
4
+ const api_js_1 = require("../lib/api.js");
5
+ const session_js_1 = require("../lib/session.js");
6
+ async function whoami() {
7
+ const { default: chalk } = await import("chalk");
8
+ const accessToken = await (0, session_js_1.requireAccessToken)();
9
+ const me = await (0, api_js_1.getMe)(accessToken);
10
+ console.log(`${me.email}${me.name ? chalk.dim(` (${me.name})`) : ""}`);
11
+ }
package/dist/index.js CHANGED
@@ -8,6 +8,12 @@ const run_js_1 = require("./commands/run.js");
8
8
  const proxy_js_1 = require("./commands/proxy.js");
9
9
  const logout_js_1 = require("./commands/logout.js");
10
10
  const status_js_1 = require("./commands/status.js");
11
+ const sets_js_1 = require("./commands/sets.js");
12
+ const use_js_1 = require("./commands/use.js");
13
+ const keys_js_1 = require("./commands/keys.js");
14
+ const doctor_js_1 = require("./commands/doctor.js");
15
+ const open_js_1 = require("./commands/open.js");
16
+ const whoami_js_1 = require("./commands/whoami.js");
11
17
  const program = new commander_1.Command();
12
18
  program
13
19
  .name("elding")
@@ -45,10 +51,11 @@ program
45
51
  program
46
52
  .command("proxy <cmd>")
47
53
  .description("Lancer une commande derrière un proxy local qui injecte les clés — jamais en mémoire de l'app")
54
+ .option("-v, --verbose", "Logger chaque requête (méthode/host/status/latence)")
48
55
  .allowUnknownOption()
49
56
  .action(async (cmd, options, command) => {
50
57
  const args = command.args.slice(1);
51
- await (0, proxy_js_1.proxy)(cmd, args).catch((err) => {
58
+ await (0, proxy_js_1.proxy)(cmd, args, !!options.verbose).catch((err) => {
52
59
  console.error(err.message);
53
60
  process.exit(1);
54
61
  });
@@ -71,4 +78,58 @@ program
71
78
  process.exit(1);
72
79
  });
73
80
  });
81
+ program
82
+ .command("sets")
83
+ .description("Lister les sets accessibles")
84
+ .action(async () => {
85
+ await (0, sets_js_1.sets)().catch((err) => {
86
+ console.error(err.message);
87
+ process.exit(1);
88
+ });
89
+ });
90
+ program
91
+ .command("use <name>")
92
+ .description("Changer le set actif du projet")
93
+ .action(async (name) => {
94
+ await (0, use_js_1.use)(name).catch((err) => {
95
+ console.error(err.message);
96
+ process.exit(1);
97
+ });
98
+ });
99
+ program
100
+ .command("keys")
101
+ .description("Lister les noms de clés du set actif (jamais les valeurs)")
102
+ .action(async () => {
103
+ await (0, keys_js_1.keys)().catch((err) => {
104
+ console.error(err.message);
105
+ process.exit(1);
106
+ });
107
+ });
108
+ program
109
+ .command("doctor")
110
+ .description("Diagnostiquer la configuration (connexion, set, clés, proxy)")
111
+ .action(async () => {
112
+ await (0, doctor_js_1.doctor)().catch((err) => {
113
+ console.error(err.message);
114
+ process.exit(1);
115
+ });
116
+ });
117
+ program
118
+ .command("open")
119
+ .description("Ouvrir le vault dans le navigateur")
120
+ .action(async () => {
121
+ await (0, open_js_1.open)().catch((err) => {
122
+ console.error(err.message);
123
+ process.exit(1);
124
+ });
125
+ });
126
+ program
127
+ .command("whoami")
128
+ .description("Afficher l'utilisateur connecté")
129
+ .action(async () => {
130
+ await (0, whoami_js_1.whoami)().catch((err) => {
131
+ console.error(err.message);
132
+ process.exit(1);
133
+ });
134
+ });
74
135
  program.parse();
package/dist/lib/api.d.ts CHANGED
@@ -1,7 +1,13 @@
1
+ export declare const BASE_URL: string;
1
2
  export type ApiKeySetItem = {
2
3
  id: string;
3
4
  name: string;
4
5
  };
6
+ export type KeyItem = {
7
+ name: string;
8
+ allowedHost: string | null;
9
+ };
10
+ export declare function listKeys(accessToken: string, setId: string): Promise<KeyItem[]>;
5
11
  export declare function exchangeToken(refreshToken: string): Promise<string>;
6
12
  export declare function revokeToken(refreshToken: string): Promise<void>;
7
13
  export declare function getMe(accessToken: string): Promise<{
@@ -13,4 +19,4 @@ export type SecretsResult = {
13
19
  secrets: Record<string, string>;
14
20
  hosts: Record<string, string>;
15
21
  };
16
- export declare function fetchSecrets(accessToken: string, setId: string): Promise<SecretsResult>;
22
+ export declare function fetchSecrets(accessToken: string, setId: string, mode?: "run" | "proxy"): Promise<SecretsResult>;
package/dist/lib/api.js CHANGED
@@ -1,13 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BASE_URL = void 0;
4
+ exports.listKeys = listKeys;
3
5
  exports.exchangeToken = exchangeToken;
4
6
  exports.revokeToken = revokeToken;
5
7
  exports.getMe = getMe;
6
8
  exports.listSets = listSets;
7
9
  exports.fetchSecrets = fetchSecrets;
8
- const BASE_URL = process.env.ELDING_API_URL ?? "https://app.elding.io";
10
+ exports.BASE_URL = process.env.ELDING_API_URL ?? "https://app.elding.io";
11
+ async function listKeys(accessToken, setId) {
12
+ const res = await fetch(`${exports.BASE_URL}/api/cli/keys?setId=${encodeURIComponent(setId)}`, {
13
+ headers: { Authorization: `Bearer ${accessToken}` },
14
+ });
15
+ if (!res.ok)
16
+ throw new Error("Impossible de récupérer les clés");
17
+ const body = (await res.json());
18
+ return Array.isArray(body.keys) ? body.keys : [];
19
+ }
9
20
  async function exchangeToken(refreshToken) {
10
- const res = await fetch(`${BASE_URL}/api/cli/auth/token`, {
21
+ const res = await fetch(`${exports.BASE_URL}/api/cli/auth/token`, {
11
22
  method: "POST",
12
23
  headers: { "Content-Type": "application/json" },
13
24
  body: JSON.stringify({ refreshToken }),
@@ -18,14 +29,14 @@ async function exchangeToken(refreshToken) {
18
29
  return body.accessToken;
19
30
  }
20
31
  async function revokeToken(refreshToken) {
21
- await fetch(`${BASE_URL}/api/cli/auth/logout`, {
32
+ await fetch(`${exports.BASE_URL}/api/cli/auth/logout`, {
22
33
  method: "POST",
23
34
  headers: { "Content-Type": "application/json" },
24
35
  body: JSON.stringify({ refreshToken }),
25
36
  }).catch(() => { });
26
37
  }
27
38
  async function getMe(accessToken) {
28
- const res = await fetch(`${BASE_URL}/api/cli/me`, {
39
+ const res = await fetch(`${exports.BASE_URL}/api/cli/me`, {
29
40
  headers: { Authorization: `Bearer ${accessToken}` },
30
41
  });
31
42
  if (!res.ok)
@@ -36,7 +47,7 @@ async function getMe(accessToken) {
36
47
  return body.user;
37
48
  }
38
49
  async function listSets(accessToken) {
39
- const res = await fetch(`${BASE_URL}/api/cli/sets`, {
50
+ const res = await fetch(`${exports.BASE_URL}/api/cli/sets`, {
40
51
  headers: { Authorization: `Bearer ${accessToken}` },
41
52
  });
42
53
  if (!res.ok)
@@ -44,10 +55,8 @@ async function listSets(accessToken) {
44
55
  const body = (await res.json());
45
56
  return Array.isArray(body.sets) ? body.sets : [];
46
57
  }
47
- async function fetchSecrets(accessToken, setId) {
48
- const res = await fetch(`${BASE_URL}/api/cli/secrets?setId=${encodeURIComponent(setId)}`, {
49
- headers: { Authorization: `Bearer ${accessToken}` },
50
- });
58
+ async function fetchSecrets(accessToken, setId, mode = "proxy") {
59
+ const res = await fetch(`${exports.BASE_URL}/api/cli/secrets?setId=${encodeURIComponent(setId)}&mode=${mode}`, { headers: { Authorization: `Bearer ${accessToken}` } });
51
60
  if (!res.ok) {
52
61
  const body = (await res.json().catch(() => ({})));
53
62
  throw new Error(body.error ?? `Erreur ${res.status}`);
@@ -3,4 +3,4 @@ export type ProxyServer = {
3
3
  token: string;
4
4
  close: () => void;
5
5
  };
6
- export declare function startProxy(secrets: Record<string, string>, hosts?: Record<string, string>): Promise<ProxyServer>;
6
+ export declare function startProxy(secrets: Record<string, string>, hosts?: Record<string, string>, verbose?: boolean): Promise<ProxyServer>;
@@ -27,8 +27,22 @@ function isBlockedHost(hostname) {
27
27
  return false;
28
28
  }
29
29
  // hosts: map nom_secret -> domaine autorisé. Si défini, le secret ne peut être envoyé qu'à ce host.
30
- function startProxy(secrets, hosts = {}) {
30
+ function startProxy(secrets, hosts = {}, verbose = false) {
31
31
  const token = (0, crypto_1.randomBytes)(24).toString("hex");
32
+ // Log une ligne par requête — jamais les valeurs, seulement les placeholders référencés
33
+ const log = (method, host, path, status, ms, note = "") => {
34
+ if (!verbose)
35
+ return;
36
+ const code = status === "BLOCKED" ? "BLOCKED" : String(status);
37
+ console.error(`[elding] ${method} ${host}${path} → ${code} ${ms}ms${note ? " " + note : ""}`);
38
+ };
39
+ const placeholdersIn = (headers) => {
40
+ const names = new Set();
41
+ for (const v of Object.values(headers))
42
+ for (const m of v.matchAll(PLACEHOLDER))
43
+ names.add(m[1]);
44
+ return names.size ? `{{${[...names].join(",")}}}` : "";
45
+ };
32
46
  // Vérifie que chaque placeholder utilisé est autorisé pour ce host. Retourne le nom fautif, ou null.
33
47
  const findHostViolation = (val, targetHost) => {
34
48
  for (const m of val.matchAll(PLACEHOLDER)) {
@@ -41,6 +55,7 @@ function startProxy(secrets, hosts = {}) {
41
55
  };
42
56
  const substitute = (val) => val.replace(PLACEHOLDER, (_, name) => secrets[name] ?? "");
43
57
  const server = http_1.default.createServer(async (req, res) => {
58
+ const started = Date.now();
44
59
  try {
45
60
  if (req.headers["x-elding-token"] !== token) {
46
61
  res.writeHead(401);
@@ -68,6 +83,7 @@ function startProxy(secrets, hosts = {}) {
68
83
  return;
69
84
  }
70
85
  if (isBlockedHost(targetUrl.hostname)) {
86
+ log(req.method ?? "?", targetUrl.hostname, req.url ?? "/", "BLOCKED", Date.now() - started, "host bloqué");
71
87
  res.writeHead(403);
72
88
  res.end("blocked host");
73
89
  return;
@@ -81,20 +97,24 @@ function startProxy(secrets, hosts = {}) {
81
97
  continue;
82
98
  const bad = findHostViolation(v, targetUrl.hostname);
83
99
  if (bad) {
100
+ log(req.method ?? "?", targetUrl.hostname, upstream.pathname, "BLOCKED", Date.now() - started, `${bad} non autorisé`);
84
101
  res.writeHead(403);
85
102
  res.end(`secret "${bad}" non autorisé pour ${targetUrl.hostname}`);
86
103
  return;
87
104
  }
88
105
  }
89
106
  const headers = {};
107
+ const rawHeaders = {};
90
108
  for (const [k, v] of Object.entries(req.headers)) {
91
109
  const lk = k.toLowerCase();
92
110
  if (lk.startsWith("x-elding-"))
93
111
  continue;
94
112
  if (lk === "host" || lk === "connection" || lk === "content-length")
95
113
  continue;
96
- if (typeof v === "string")
114
+ if (typeof v === "string") {
115
+ rawHeaders[k] = v;
97
116
  headers[k] = substitute(v);
117
+ }
98
118
  }
99
119
  const chunks = [];
100
120
  for await (const c of req)
@@ -105,6 +125,7 @@ function startProxy(secrets, hosts = {}) {
105
125
  headers,
106
126
  body: hasBody ? Buffer.concat(chunks) : undefined,
107
127
  });
128
+ log(req.method ?? "?", targetUrl.hostname, upstream.pathname, upstreamRes.status, Date.now() - started, placeholdersIn(rawHeaders));
108
129
  res.writeHead(upstreamRes.status, Object.fromEntries(upstreamRes.headers));
109
130
  if (upstreamRes.body) {
110
131
  const reader = upstreamRes.body.getReader();
@@ -0,0 +1 @@
1
+ export declare function requireAccessToken(): Promise<string>;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireAccessToken = requireAccessToken;
4
+ const config_js_1 = require("./config.js");
5
+ const api_js_1 = require("./api.js");
6
+ // Lit le token local, l'échange contre un access token. Lève une erreur claire sinon.
7
+ async function requireAccessToken() {
8
+ const config = (0, config_js_1.readConfig)();
9
+ if (!config)
10
+ throw new Error("Non connecté. Lancez `elding login` d'abord.");
11
+ try {
12
+ return await (0, api_js_1.exchangeToken)(config.refreshToken);
13
+ }
14
+ catch {
15
+ throw new Error("Token expiré ou révoqué. Relancez `elding login`.");
16
+ }
17
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elding/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Elding CLI — zero .env, secrets from vault",
5
5
  "bin": {
6
6
  "elding": "./dist/index.js"