@keynv/cli 0.1.0-rc.11 → 0.1.0-rc.13
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 +1059 -618
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
|
-
import 'crypto';
|
|
3
|
+
import { randomBytes } from 'crypto';
|
|
4
4
|
import { readFileSync, existsSync, statSync, writeFileSync, unlinkSync, mkdirSync, rmSync, readdirSync } from 'fs';
|
|
5
5
|
import { hostname, platform, homedir } from 'os';
|
|
6
|
-
import { isAbsolute, resolve, join, dirname, basename } from 'path';
|
|
6
|
+
import { relative, isAbsolute, resolve, join, dirname, basename } from 'path';
|
|
7
7
|
import { Entry } from '@napi-rs/keyring';
|
|
8
8
|
import { styleText } from 'util';
|
|
9
9
|
import j2, { stdin, stdout } from 'process';
|
|
@@ -1056,7 +1056,7 @@ var require_lib = __commonJS({
|
|
|
1056
1056
|
var VERSION, AGENT;
|
|
1057
1057
|
var init_version = __esm({
|
|
1058
1058
|
"src/version.ts"() {
|
|
1059
|
-
VERSION = "0.1.0-rc.
|
|
1059
|
+
VERSION = "0.1.0-rc.13" ;
|
|
1060
1060
|
AGENT = `keynv-cli/${VERSION}`;
|
|
1061
1061
|
}
|
|
1062
1062
|
});
|
|
@@ -1146,10 +1146,10 @@ var init_parser = __esm({
|
|
|
1146
1146
|
"../../packages/core/dist/reference/parser.js"() {
|
|
1147
1147
|
PROJECT_RE = /^[a-z0-9][a-z0-9-]{0,47}$/;
|
|
1148
1148
|
ENV_RE = /^[a-z0-9][a-z0-9-]{0,23}$/;
|
|
1149
|
-
KEY_RE = /^[a-
|
|
1149
|
+
KEY_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/;
|
|
1150
1150
|
TRAILING_BOUNDARY = "(?!\\w|\\.[a-z0-9])";
|
|
1151
|
-
TEXT_FIND_SOURCE = `(?<![\\w.@/])@[a-z0-9][a-z0-9-]{0,47}\\.[a-z0-9][a-z0-9-]{0,23}\\.[a-
|
|
1152
|
-
ARGV_FIND_SOURCE = `@[a-z0-9][a-z0-9-]{0,47}\\.[a-z0-9][a-z0-9-]{0,23}\\.[a-
|
|
1151
|
+
TEXT_FIND_SOURCE = `(?<![\\w.@/])@[a-z0-9][a-z0-9-]{0,47}\\.[a-z0-9][a-z0-9-]{0,23}\\.[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}${TRAILING_BOUNDARY}`;
|
|
1152
|
+
ARGV_FIND_SOURCE = `@[a-z0-9][a-z0-9-]{0,47}\\.[a-z0-9][a-z0-9-]{0,23}\\.[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}${TRAILING_BOUNDARY}`;
|
|
1153
1153
|
TEXT_FIND_RE = new RegExp(TEXT_FIND_SOURCE, "g");
|
|
1154
1154
|
ARGV_FIND_RE = new RegExp(ARGV_FIND_SOURCE, "g");
|
|
1155
1155
|
}
|
|
@@ -5536,7 +5536,15 @@ function clearCredentialsFile() {
|
|
|
5536
5536
|
if (existsSync(legacy)) rmSync(legacy, { force: true });
|
|
5537
5537
|
try {
|
|
5538
5538
|
entry().deletePassword();
|
|
5539
|
-
|
|
5539
|
+
return true;
|
|
5540
|
+
} catch (err) {
|
|
5541
|
+
process.stderr.write(
|
|
5542
|
+
`keynv: warning \u2014 could not remove OS keychain entry (${err instanceof Error ? err.message : String(err)}).
|
|
5543
|
+
You may need to remove the '${SERVICE}' / '${KEY_ACCOUNT}' entry manually
|
|
5544
|
+
via your OS credential manager (Windows Credential Manager, macOS Keychain, or libsecret).
|
|
5545
|
+
`
|
|
5546
|
+
);
|
|
5547
|
+
return false;
|
|
5540
5548
|
}
|
|
5541
5549
|
}
|
|
5542
5550
|
var SERVICE, KEY_ACCOUNT;
|
|
@@ -5579,8 +5587,9 @@ async function saveCredentials(creds) {
|
|
|
5579
5587
|
cache = creds;
|
|
5580
5588
|
}
|
|
5581
5589
|
function clearCredentials() {
|
|
5582
|
-
clearCredentialsFile();
|
|
5590
|
+
const ok = clearCredentialsFile();
|
|
5583
5591
|
cache = null;
|
|
5592
|
+
return ok;
|
|
5584
5593
|
}
|
|
5585
5594
|
var cache;
|
|
5586
5595
|
var init_store = __esm({
|
|
@@ -5590,6 +5599,9 @@ var init_store = __esm({
|
|
|
5590
5599
|
});
|
|
5591
5600
|
|
|
5592
5601
|
// src/client/http.ts
|
|
5602
|
+
function isClientError(err) {
|
|
5603
|
+
return err instanceof Error && typeof err.status === "number";
|
|
5604
|
+
}
|
|
5593
5605
|
function clientError(status, code, message, details) {
|
|
5594
5606
|
const err = Object.assign(new Error(message), { status, code, details });
|
|
5595
5607
|
return err;
|
|
@@ -5719,8 +5731,38 @@ function fmtError(err) {
|
|
|
5719
5731
|
const status = err.status ? ` (${err.status})` : "";
|
|
5720
5732
|
return `keynv:${code}${status} ${err.message}`;
|
|
5721
5733
|
}
|
|
5734
|
+
function handleExecError(stderr, err) {
|
|
5735
|
+
if (isClientError(err)) {
|
|
5736
|
+
const code = err.code ? `[${err.code}] ` : "";
|
|
5737
|
+
stderr.write(`keynv: ${code}${err.message}
|
|
5738
|
+
`);
|
|
5739
|
+
} else if (err instanceof Error) {
|
|
5740
|
+
stderr.write(`keynv: ${err.message}
|
|
5741
|
+
`);
|
|
5742
|
+
} else {
|
|
5743
|
+
stderr.write(`keynv: unexpected error
|
|
5744
|
+
`);
|
|
5745
|
+
}
|
|
5746
|
+
return 1;
|
|
5747
|
+
}
|
|
5722
5748
|
var init_format = __esm({
|
|
5723
5749
|
"src/ui/format.ts"() {
|
|
5750
|
+
init_http();
|
|
5751
|
+
}
|
|
5752
|
+
});
|
|
5753
|
+
function walkUp(startDir, predicate) {
|
|
5754
|
+
let dir = resolve(startDir);
|
|
5755
|
+
for (let i = 0; i < 64; i++) {
|
|
5756
|
+
const result = predicate(dir);
|
|
5757
|
+
if (result !== null && result !== void 0) return result;
|
|
5758
|
+
const parent = dirname(dir);
|
|
5759
|
+
if (parent === dir) return null;
|
|
5760
|
+
dir = parent;
|
|
5761
|
+
}
|
|
5762
|
+
return null;
|
|
5763
|
+
}
|
|
5764
|
+
var init_fs = __esm({
|
|
5765
|
+
"src/util/fs.ts"() {
|
|
5724
5766
|
}
|
|
5725
5767
|
});
|
|
5726
5768
|
function parseEnvFile(content, filename) {
|
|
@@ -5781,20 +5823,14 @@ function parseEnvFile(content, filename) {
|
|
|
5781
5823
|
return entries;
|
|
5782
5824
|
}
|
|
5783
5825
|
function findEnvFile(startDir) {
|
|
5784
|
-
|
|
5785
|
-
for (let i = 0; i < 64; i++) {
|
|
5826
|
+
return walkUp(startDir, (dir) => {
|
|
5786
5827
|
const candidate = join(dir, ENV_FILE_BASENAME);
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
} catch {
|
|
5791
|
-
}
|
|
5828
|
+
try {
|
|
5829
|
+
if (statSync(candidate).isFile()) return candidate;
|
|
5830
|
+
} catch {
|
|
5792
5831
|
}
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
dir = parent;
|
|
5796
|
-
}
|
|
5797
|
-
return null;
|
|
5832
|
+
return null;
|
|
5833
|
+
});
|
|
5798
5834
|
}
|
|
5799
5835
|
function loadEnvFile(opts) {
|
|
5800
5836
|
if (opts.disabled) return null;
|
|
@@ -5819,6 +5855,7 @@ var MAX_FILE_BYTES, KEY_RE2, ENV_FILE_BASENAME, EnvFileParseError, EnvFileNotFou
|
|
|
5819
5855
|
var init_envFile = __esm({
|
|
5820
5856
|
"src/exec/envFile.ts"() {
|
|
5821
5857
|
init_dist();
|
|
5858
|
+
init_fs();
|
|
5822
5859
|
MAX_FILE_BYTES = 1e6;
|
|
5823
5860
|
KEY_RE2 = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
5824
5861
|
ENV_FILE_BASENAME = ".keynv.env";
|
|
@@ -5855,6 +5892,132 @@ var init_envFile = __esm({
|
|
|
5855
5892
|
}
|
|
5856
5893
|
});
|
|
5857
5894
|
|
|
5895
|
+
// src/init/heuristics.ts
|
|
5896
|
+
function shannonEntropyBits(s) {
|
|
5897
|
+
if (s.length === 0) return 0;
|
|
5898
|
+
const counts = /* @__PURE__ */ new Map();
|
|
5899
|
+
for (const ch of s) counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
5900
|
+
let h2 = 0;
|
|
5901
|
+
for (const c2 of counts.values()) {
|
|
5902
|
+
const p2 = c2 / s.length;
|
|
5903
|
+
h2 -= p2 * Math.log2(p2);
|
|
5904
|
+
}
|
|
5905
|
+
return h2;
|
|
5906
|
+
}
|
|
5907
|
+
function nameMatchesLiteralPrefix(name) {
|
|
5908
|
+
const upper = name.toUpperCase();
|
|
5909
|
+
return NAME_LITERAL_PREFIX.some((p2) => upper.startsWith(p2));
|
|
5910
|
+
}
|
|
5911
|
+
function nameMatchesSecretSuffix(name) {
|
|
5912
|
+
const upper = name.toUpperCase();
|
|
5913
|
+
for (const { suffix, hint } of NAME_SECRET_SUFFIXES) {
|
|
5914
|
+
if (upper.endsWith(suffix) || upper.endsWith(`_${suffix}`)) {
|
|
5915
|
+
return { matched: true, hint };
|
|
5916
|
+
}
|
|
5917
|
+
}
|
|
5918
|
+
return { matched: false, hint: "" };
|
|
5919
|
+
}
|
|
5920
|
+
function classifyEntry(name, value) {
|
|
5921
|
+
const upper = name.toUpperCase();
|
|
5922
|
+
if (NAME_FRAMEWORK_MANAGED.has(upper)) {
|
|
5923
|
+
return { verdict: "skip", hint: "framework/shell-managed" };
|
|
5924
|
+
}
|
|
5925
|
+
if (NAME_LITERAL_EXACT.has(upper)) {
|
|
5926
|
+
return { verdict: "literal", hint: "common config var" };
|
|
5927
|
+
}
|
|
5928
|
+
if (nameMatchesLiteralPrefix(name)) {
|
|
5929
|
+
return { verdict: "literal", hint: "public env, build-time bundled" };
|
|
5930
|
+
}
|
|
5931
|
+
if (value.length === 0) {
|
|
5932
|
+
return { verdict: "literal", hint: "empty" };
|
|
5933
|
+
}
|
|
5934
|
+
for (const { re, hint } of VALUE_PATTERNS) {
|
|
5935
|
+
if (re.test(value)) return { verdict: "secret", hint };
|
|
5936
|
+
}
|
|
5937
|
+
const suffix = nameMatchesSecretSuffix(name);
|
|
5938
|
+
if (suffix.matched) {
|
|
5939
|
+
return { verdict: "secret", hint: suffix.hint };
|
|
5940
|
+
}
|
|
5941
|
+
if (NAME_DB_URL.test(name)) {
|
|
5942
|
+
return { verdict: "secret", hint: "database URL" };
|
|
5943
|
+
}
|
|
5944
|
+
if (value.length >= 32) {
|
|
5945
|
+
const bits = shannonEntropyBits(value);
|
|
5946
|
+
if (bits >= 3.5) {
|
|
5947
|
+
return { verdict: "secret", hint: `${value.length}-char random-looking string` };
|
|
5948
|
+
}
|
|
5949
|
+
}
|
|
5950
|
+
return { verdict: "ambiguous", hint: "" };
|
|
5951
|
+
}
|
|
5952
|
+
function previewValue(value, max = 40) {
|
|
5953
|
+
if (value.length === 0) return "(empty)";
|
|
5954
|
+
if (value.length <= max) return value;
|
|
5955
|
+
return `${value.slice(0, max - 1)}\u2026`;
|
|
5956
|
+
}
|
|
5957
|
+
var NAME_FRAMEWORK_MANAGED, NAME_LITERAL_EXACT, NAME_LITERAL_PREFIX, NAME_SECRET_SUFFIXES, NAME_DB_URL, VALUE_PATTERNS;
|
|
5958
|
+
var init_heuristics = __esm({
|
|
5959
|
+
"src/init/heuristics.ts"() {
|
|
5960
|
+
NAME_FRAMEWORK_MANAGED = /* @__PURE__ */ new Set([
|
|
5961
|
+
"NODE_ENV",
|
|
5962
|
+
"PORT",
|
|
5963
|
+
"HOST",
|
|
5964
|
+
"HOSTNAME",
|
|
5965
|
+
"PATH",
|
|
5966
|
+
"HOME",
|
|
5967
|
+
"USER",
|
|
5968
|
+
"SHELL",
|
|
5969
|
+
"PWD",
|
|
5970
|
+
"CI"
|
|
5971
|
+
]);
|
|
5972
|
+
NAME_LITERAL_EXACT = /* @__PURE__ */ new Set([
|
|
5973
|
+
"DEBUG",
|
|
5974
|
+
"LOG_LEVEL",
|
|
5975
|
+
"LOGLEVEL",
|
|
5976
|
+
"TZ",
|
|
5977
|
+
"LANG",
|
|
5978
|
+
"LC_ALL",
|
|
5979
|
+
"NODE_OPTIONS",
|
|
5980
|
+
"NPM_CONFIG_LOGLEVEL",
|
|
5981
|
+
"TS_NODE_PROJECT"
|
|
5982
|
+
]);
|
|
5983
|
+
NAME_LITERAL_PREFIX = ["NEXT_PUBLIC_", "VITE_", "REACT_APP_", "PUBLIC_", "EXPO_PUBLIC_"];
|
|
5984
|
+
NAME_SECRET_SUFFIXES = [
|
|
5985
|
+
{ suffix: "PRIVATE_KEY", hint: "private key" },
|
|
5986
|
+
{ suffix: "API_KEY", hint: "API key" },
|
|
5987
|
+
{ suffix: "ACCESS_KEY", hint: "access key" },
|
|
5988
|
+
{ suffix: "SECRET_KEY", hint: "secret key" },
|
|
5989
|
+
{ suffix: "SECRET", hint: "secret" },
|
|
5990
|
+
{ suffix: "PASSWORD", hint: "password" },
|
|
5991
|
+
{ suffix: "PASSPHRASE", hint: "passphrase" },
|
|
5992
|
+
{ suffix: "TOKEN", hint: "token" },
|
|
5993
|
+
{ suffix: "KEY", hint: "key" },
|
|
5994
|
+
{ suffix: "CREDENTIALS", hint: "credentials" },
|
|
5995
|
+
{ suffix: "AUTH", hint: "auth" },
|
|
5996
|
+
{ suffix: "DSN", hint: "connection string" }
|
|
5997
|
+
];
|
|
5998
|
+
NAME_DB_URL = /^(DATABASE|DB|POSTGRES|MYSQL|MONGO|REDIS)_URL$/i;
|
|
5999
|
+
VALUE_PATTERNS = [
|
|
6000
|
+
{ re: /^sk-proj-/, hint: "OpenAI project key" },
|
|
6001
|
+
{ re: /^sk-[A-Za-z0-9]{20,}/, hint: "OpenAI key" },
|
|
6002
|
+
{ re: /^sk_live_/, hint: "Stripe live key" },
|
|
6003
|
+
{ re: /^sk_test_/, hint: "Stripe test key" },
|
|
6004
|
+
{ re: /^pk_live_/, hint: "Stripe publishable \u2014 often public, double-check" },
|
|
6005
|
+
{ re: /^xoxb-/, hint: "Slack bot token" },
|
|
6006
|
+
{ re: /^xoxp-/, hint: "Slack user token" },
|
|
6007
|
+
{ re: /^ghp_[A-Za-z0-9]{30,}/, hint: "GitHub personal access token" },
|
|
6008
|
+
{ re: /^github_pat_/, hint: "GitHub fine-grained PAT" },
|
|
6009
|
+
{ re: /^gho_/, hint: "GitHub OAuth token" },
|
|
6010
|
+
{ re: /^AKIA[0-9A-Z]{16}$/, hint: "AWS access key id" },
|
|
6011
|
+
{ re: /^ASIA[0-9A-Z]{16}$/, hint: "AWS temporary key id" },
|
|
6012
|
+
{ re: /^AIza[0-9A-Za-z_-]{35}$/, hint: "Google API key" },
|
|
6013
|
+
{ re: /^ya29\./, hint: "Google OAuth access token" },
|
|
6014
|
+
{ re: /^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/, hint: "JWT" },
|
|
6015
|
+
{ re: /^-----BEGIN [A-Z ]+PRIVATE KEY-----/, hint: "PEM private key" },
|
|
6016
|
+
{ re: /^([a-z]+):\/\/[^@]+:[^@]+@/i, hint: "connection string with credentials" }
|
|
6017
|
+
];
|
|
6018
|
+
}
|
|
6019
|
+
});
|
|
6020
|
+
|
|
5858
6021
|
// ../../node_modules/.pnpm/fast-string-truncated-width@3.0.3/node_modules/fast-string-truncated-width/dist/utils.js
|
|
5859
6022
|
var getCodePointsLength, isFullWidth, isWideNotCJKTNotEmoji;
|
|
5860
6023
|
var init_utils = __esm({
|
|
@@ -7099,22 +7262,19 @@ var init_aiContext = __esm({
|
|
|
7099
7262
|
}
|
|
7100
7263
|
});
|
|
7101
7264
|
function findProjectRoot(startDir) {
|
|
7102
|
-
|
|
7103
|
-
for (let i = 0; i < 64; i++) {
|
|
7265
|
+
const result = walkUp(startDir, (dir) => {
|
|
7104
7266
|
for (const marker of PROJECT_MARKERS) {
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
return buildRoot(dir, marker);
|
|
7267
|
+
if (existsSync(join(dir, marker))) {
|
|
7268
|
+
return { dir, marker };
|
|
7108
7269
|
}
|
|
7109
7270
|
}
|
|
7110
7271
|
if (existsSync(join(dir, GIT_MARKER))) {
|
|
7111
|
-
return
|
|
7272
|
+
return { dir, marker: GIT_MARKER };
|
|
7112
7273
|
}
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
return null;
|
|
7274
|
+
return null;
|
|
7275
|
+
});
|
|
7276
|
+
if (!result) return null;
|
|
7277
|
+
return buildRoot(result.dir, result.marker);
|
|
7118
7278
|
}
|
|
7119
7279
|
function buildRoot(dir, marker) {
|
|
7120
7280
|
let suggestedName = basename(dir);
|
|
@@ -7199,6 +7359,7 @@ function hasExistingKeynvEnv(rootDir) {
|
|
|
7199
7359
|
var PROJECT_MARKERS, GIT_MARKER, ENV_GLOB, ENV_EXAMPLE, KEYNV_ENV_BASENAME;
|
|
7200
7360
|
var init_detect = __esm({
|
|
7201
7361
|
"src/init/detect.ts"() {
|
|
7362
|
+
init_fs();
|
|
7202
7363
|
PROJECT_MARKERS = [
|
|
7203
7364
|
"package.json",
|
|
7204
7365
|
"pyproject.toml",
|
|
@@ -7216,132 +7377,6 @@ var init_detect = __esm({
|
|
|
7216
7377
|
}
|
|
7217
7378
|
});
|
|
7218
7379
|
|
|
7219
|
-
// src/init/heuristics.ts
|
|
7220
|
-
function shannonEntropyBits(s) {
|
|
7221
|
-
if (s.length === 0) return 0;
|
|
7222
|
-
const counts = /* @__PURE__ */ new Map();
|
|
7223
|
-
for (const ch of s) counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
7224
|
-
let h2 = 0;
|
|
7225
|
-
for (const c2 of counts.values()) {
|
|
7226
|
-
const p2 = c2 / s.length;
|
|
7227
|
-
h2 -= p2 * Math.log2(p2);
|
|
7228
|
-
}
|
|
7229
|
-
return h2;
|
|
7230
|
-
}
|
|
7231
|
-
function nameMatchesLiteralPrefix(name) {
|
|
7232
|
-
const upper = name.toUpperCase();
|
|
7233
|
-
return NAME_LITERAL_PREFIX.some((p2) => upper.startsWith(p2));
|
|
7234
|
-
}
|
|
7235
|
-
function nameMatchesSecretSuffix(name) {
|
|
7236
|
-
const upper = name.toUpperCase();
|
|
7237
|
-
for (const { suffix, hint } of NAME_SECRET_SUFFIXES) {
|
|
7238
|
-
if (upper.endsWith(suffix) || upper.endsWith(`_${suffix}`)) {
|
|
7239
|
-
return { matched: true, hint };
|
|
7240
|
-
}
|
|
7241
|
-
}
|
|
7242
|
-
return { matched: false, hint: "" };
|
|
7243
|
-
}
|
|
7244
|
-
function classifyEntry(name, value) {
|
|
7245
|
-
const upper = name.toUpperCase();
|
|
7246
|
-
if (NAME_FRAMEWORK_MANAGED.has(upper)) {
|
|
7247
|
-
return { verdict: "skip", hint: "framework/shell-managed" };
|
|
7248
|
-
}
|
|
7249
|
-
if (NAME_LITERAL_EXACT.has(upper)) {
|
|
7250
|
-
return { verdict: "literal", hint: "common config var" };
|
|
7251
|
-
}
|
|
7252
|
-
if (nameMatchesLiteralPrefix(name)) {
|
|
7253
|
-
return { verdict: "literal", hint: "public env, build-time bundled" };
|
|
7254
|
-
}
|
|
7255
|
-
if (value.length === 0) {
|
|
7256
|
-
return { verdict: "literal", hint: "empty" };
|
|
7257
|
-
}
|
|
7258
|
-
for (const { re, hint } of VALUE_PATTERNS) {
|
|
7259
|
-
if (re.test(value)) return { verdict: "secret", hint };
|
|
7260
|
-
}
|
|
7261
|
-
const suffix = nameMatchesSecretSuffix(name);
|
|
7262
|
-
if (suffix.matched) {
|
|
7263
|
-
return { verdict: "secret", hint: suffix.hint };
|
|
7264
|
-
}
|
|
7265
|
-
if (NAME_DB_URL.test(name)) {
|
|
7266
|
-
return { verdict: "secret", hint: "database URL" };
|
|
7267
|
-
}
|
|
7268
|
-
if (value.length >= 32) {
|
|
7269
|
-
const bits = shannonEntropyBits(value);
|
|
7270
|
-
if (bits >= 3.5) {
|
|
7271
|
-
return { verdict: "secret", hint: `${value.length}-char random-looking string` };
|
|
7272
|
-
}
|
|
7273
|
-
}
|
|
7274
|
-
return { verdict: "ambiguous", hint: "" };
|
|
7275
|
-
}
|
|
7276
|
-
function previewValue(value, max = 40) {
|
|
7277
|
-
if (value.length === 0) return "(empty)";
|
|
7278
|
-
if (value.length <= max) return value;
|
|
7279
|
-
return `${value.slice(0, max - 1)}\u2026`;
|
|
7280
|
-
}
|
|
7281
|
-
var NAME_FRAMEWORK_MANAGED, NAME_LITERAL_EXACT, NAME_LITERAL_PREFIX, NAME_SECRET_SUFFIXES, NAME_DB_URL, VALUE_PATTERNS;
|
|
7282
|
-
var init_heuristics = __esm({
|
|
7283
|
-
"src/init/heuristics.ts"() {
|
|
7284
|
-
NAME_FRAMEWORK_MANAGED = /* @__PURE__ */ new Set([
|
|
7285
|
-
"NODE_ENV",
|
|
7286
|
-
"PORT",
|
|
7287
|
-
"HOST",
|
|
7288
|
-
"HOSTNAME",
|
|
7289
|
-
"PATH",
|
|
7290
|
-
"HOME",
|
|
7291
|
-
"USER",
|
|
7292
|
-
"SHELL",
|
|
7293
|
-
"PWD",
|
|
7294
|
-
"CI"
|
|
7295
|
-
]);
|
|
7296
|
-
NAME_LITERAL_EXACT = /* @__PURE__ */ new Set([
|
|
7297
|
-
"DEBUG",
|
|
7298
|
-
"LOG_LEVEL",
|
|
7299
|
-
"LOGLEVEL",
|
|
7300
|
-
"TZ",
|
|
7301
|
-
"LANG",
|
|
7302
|
-
"LC_ALL",
|
|
7303
|
-
"NODE_OPTIONS",
|
|
7304
|
-
"NPM_CONFIG_LOGLEVEL",
|
|
7305
|
-
"TS_NODE_PROJECT"
|
|
7306
|
-
]);
|
|
7307
|
-
NAME_LITERAL_PREFIX = ["NEXT_PUBLIC_", "VITE_", "REACT_APP_", "PUBLIC_", "EXPO_PUBLIC_"];
|
|
7308
|
-
NAME_SECRET_SUFFIXES = [
|
|
7309
|
-
{ suffix: "PRIVATE_KEY", hint: "private key" },
|
|
7310
|
-
{ suffix: "API_KEY", hint: "API key" },
|
|
7311
|
-
{ suffix: "ACCESS_KEY", hint: "access key" },
|
|
7312
|
-
{ suffix: "SECRET_KEY", hint: "secret key" },
|
|
7313
|
-
{ suffix: "SECRET", hint: "secret" },
|
|
7314
|
-
{ suffix: "PASSWORD", hint: "password" },
|
|
7315
|
-
{ suffix: "PASSPHRASE", hint: "passphrase" },
|
|
7316
|
-
{ suffix: "TOKEN", hint: "token" },
|
|
7317
|
-
{ suffix: "KEY", hint: "key" },
|
|
7318
|
-
{ suffix: "CREDENTIALS", hint: "credentials" },
|
|
7319
|
-
{ suffix: "AUTH", hint: "auth" },
|
|
7320
|
-
{ suffix: "DSN", hint: "connection string" }
|
|
7321
|
-
];
|
|
7322
|
-
NAME_DB_URL = /^(DATABASE|DB|POSTGRES|MYSQL|MONGO|REDIS)_URL$/i;
|
|
7323
|
-
VALUE_PATTERNS = [
|
|
7324
|
-
{ re: /^sk-proj-/, hint: "OpenAI project key" },
|
|
7325
|
-
{ re: /^sk-[A-Za-z0-9]{20,}/, hint: "OpenAI key" },
|
|
7326
|
-
{ re: /^sk_live_/, hint: "Stripe live key" },
|
|
7327
|
-
{ re: /^sk_test_/, hint: "Stripe test key" },
|
|
7328
|
-
{ re: /^pk_live_/, hint: "Stripe publishable \u2014 often public, double-check" },
|
|
7329
|
-
{ re: /^xoxb-/, hint: "Slack bot token" },
|
|
7330
|
-
{ re: /^xoxp-/, hint: "Slack user token" },
|
|
7331
|
-
{ re: /^ghp_[A-Za-z0-9]{30,}/, hint: "GitHub personal access token" },
|
|
7332
|
-
{ re: /^github_pat_/, hint: "GitHub fine-grained PAT" },
|
|
7333
|
-
{ re: /^gho_/, hint: "GitHub OAuth token" },
|
|
7334
|
-
{ re: /^AKIA[0-9A-Z]{16}$/, hint: "AWS access key id" },
|
|
7335
|
-
{ re: /^ASIA[0-9A-Z]{16}$/, hint: "AWS temporary key id" },
|
|
7336
|
-
{ re: /^AIza[0-9A-Za-z_-]{35}$/, hint: "Google API key" },
|
|
7337
|
-
{ re: /^ya29\./, hint: "Google OAuth access token" },
|
|
7338
|
-
{ re: /^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/, hint: "JWT" },
|
|
7339
|
-
{ re: /^-----BEGIN [A-Z ]+PRIVATE KEY-----/, hint: "PEM private key" },
|
|
7340
|
-
{ re: /^([a-z]+):\/\/[^@]+:[^@]+@/i, hint: "connection string with credentials" }
|
|
7341
|
-
];
|
|
7342
|
-
}
|
|
7343
|
-
});
|
|
7344
|
-
|
|
7345
7380
|
// src/init/scriptWrap.ts
|
|
7346
7381
|
function analyzeScript(name, command) {
|
|
7347
7382
|
const trimmed = command.trim();
|
|
@@ -7989,7 +8024,8 @@ function composeKeynvEnv(opts) {
|
|
|
7989
8024
|
const { uploadedAliases, literals, mergeWithExisting } = opts;
|
|
7990
8025
|
const lines = [];
|
|
7991
8026
|
if (mergeWithExisting !== null) {
|
|
7992
|
-
const
|
|
8027
|
+
const normalized = mergeWithExisting.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
8028
|
+
const trimmed = normalized.replace(/\n+$/, "");
|
|
7993
8029
|
if (trimmed.length > 0) {
|
|
7994
8030
|
lines.push(...trimmed.split("\n"));
|
|
7995
8031
|
lines.push("");
|
|
@@ -8099,8 +8135,27 @@ async function runBrowserAuth(serverUrl) {
|
|
|
8099
8135
|
}
|
|
8100
8136
|
const start = await startRes.json();
|
|
8101
8137
|
const opened = openBrowser(start.verification_uri_complete);
|
|
8102
|
-
if (
|
|
8103
|
-
|
|
8138
|
+
if (opened) {
|
|
8139
|
+
process.stderr.write(
|
|
8140
|
+
`
|
|
8141
|
+
Your code: ${start.user_code}
|
|
8142
|
+
Complete auth in your browser, then return here.
|
|
8143
|
+
|
|
8144
|
+
`
|
|
8145
|
+
);
|
|
8146
|
+
} else {
|
|
8147
|
+
process.stderr.write(
|
|
8148
|
+
`
|
|
8149
|
+
Could not open a browser automatically.
|
|
8150
|
+
Open this URL manually:
|
|
8151
|
+
|
|
8152
|
+
${start.verification_uri_complete}
|
|
8153
|
+
|
|
8154
|
+
Your code: ${start.user_code}
|
|
8155
|
+
Waiting for you to complete auth in the browser...
|
|
8156
|
+
|
|
8157
|
+
`
|
|
8158
|
+
);
|
|
8104
8159
|
}
|
|
8105
8160
|
const deadline = Date.now() + start.expires_in * 1e3;
|
|
8106
8161
|
const intervalMs = Math.max(1, start.interval) * 1e3;
|
|
@@ -28679,7 +28734,7 @@ var require_utils_webcrypto = __commonJS({
|
|
|
28679
28734
|
var nodeCrypto = __require("crypto");
|
|
28680
28735
|
module.exports = {
|
|
28681
28736
|
postgresMd5PasswordHash,
|
|
28682
|
-
randomBytes,
|
|
28737
|
+
randomBytes: randomBytes2,
|
|
28683
28738
|
deriveKey,
|
|
28684
28739
|
sha256,
|
|
28685
28740
|
hashByName,
|
|
@@ -28689,7 +28744,7 @@ var require_utils_webcrypto = __commonJS({
|
|
|
28689
28744
|
var webCrypto = nodeCrypto.webcrypto || globalThis.crypto;
|
|
28690
28745
|
var subtleCrypto = webCrypto.subtle;
|
|
28691
28746
|
var textEncoder = new TextEncoder();
|
|
28692
|
-
function
|
|
28747
|
+
function randomBytes2(length) {
|
|
28693
28748
|
return webCrypto.getRandomValues(Buffer.alloc(length));
|
|
28694
28749
|
}
|
|
28695
28750
|
async function md5(string) {
|
|
@@ -42554,7 +42609,7 @@ var init_audit2 = __esm({
|
|
|
42554
42609
|
async function runLoginFlow(client, options = {}) {
|
|
42555
42610
|
const server = options.server ?? DEFAULT_SERVER_URL;
|
|
42556
42611
|
const s = ft();
|
|
42557
|
-
s.start("
|
|
42612
|
+
s.start("Waiting for authorization");
|
|
42558
42613
|
let creds;
|
|
42559
42614
|
try {
|
|
42560
42615
|
creds = await runBrowserAuth(server);
|
|
@@ -42814,13 +42869,17 @@ async function runMenu() {
|
|
|
42814
42869
|
if (u) R2.message(`${u.email} (${u.org_role}) @ ${u.server_url}`);
|
|
42815
42870
|
}
|
|
42816
42871
|
if (didLogin) {
|
|
42817
|
-
const
|
|
42818
|
-
|
|
42819
|
-
|
|
42820
|
-
|
|
42821
|
-
|
|
42822
|
-
|
|
42823
|
-
|
|
42872
|
+
const root = findProjectRoot(process.cwd());
|
|
42873
|
+
const alreadyInitialized = root !== null && hasExistingKeynvEnv(root.path);
|
|
42874
|
+
if (!alreadyInitialized) {
|
|
42875
|
+
const setup = await ue({
|
|
42876
|
+
message: "Set up this project now?",
|
|
42877
|
+
initialValue: true
|
|
42878
|
+
});
|
|
42879
|
+
if (!q(setup) && setup) {
|
|
42880
|
+
const { runInitFlow: runInitFlow2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
42881
|
+
await runInitFlow2(client, { cwd: process.cwd(), dryRun: false, noScripts: false });
|
|
42882
|
+
}
|
|
42824
42883
|
}
|
|
42825
42884
|
}
|
|
42826
42885
|
while (true) {
|
|
@@ -42889,6 +42948,7 @@ var init_menu = __esm({
|
|
|
42889
42948
|
"src/ui/menu.ts"() {
|
|
42890
42949
|
init_dist5();
|
|
42891
42950
|
init_http();
|
|
42951
|
+
init_detect();
|
|
42892
42952
|
init_store();
|
|
42893
42953
|
init_version();
|
|
42894
42954
|
init_audit2();
|
|
@@ -44615,44 +44675,48 @@ var AuditListCommand = class extends Command {
|
|
|
44615
44675
|
sinceId = options_exports.String("--since-id");
|
|
44616
44676
|
json = options_exports.Boolean("--json", false);
|
|
44617
44677
|
async execute() {
|
|
44618
|
-
|
|
44619
|
-
|
|
44620
|
-
|
|
44621
|
-
|
|
44622
|
-
|
|
44623
|
-
|
|
44624
|
-
|
|
44625
|
-
|
|
44678
|
+
try {
|
|
44679
|
+
const client = new ApiClient();
|
|
44680
|
+
const data = await client.request(
|
|
44681
|
+
"/v1/audit",
|
|
44682
|
+
{
|
|
44683
|
+
query: {
|
|
44684
|
+
event_type: this.eventType,
|
|
44685
|
+
limit: this.limit,
|
|
44686
|
+
since_id: this.sinceId
|
|
44687
|
+
}
|
|
44626
44688
|
}
|
|
44627
|
-
|
|
44628
|
-
|
|
44629
|
-
|
|
44630
|
-
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
44689
|
+
);
|
|
44690
|
+
if (this.json) {
|
|
44691
|
+
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
44631
44692
|
`);
|
|
44632
|
-
|
|
44633
|
-
|
|
44634
|
-
|
|
44635
|
-
|
|
44636
|
-
|
|
44637
|
-
|
|
44638
|
-
|
|
44639
|
-
|
|
44640
|
-
|
|
44641
|
-
|
|
44642
|
-
|
|
44643
|
-
|
|
44644
|
-
|
|
44645
|
-
|
|
44646
|
-
|
|
44647
|
-
|
|
44648
|
-
|
|
44693
|
+
return 0;
|
|
44694
|
+
}
|
|
44695
|
+
if (data.entries.length === 0) {
|
|
44696
|
+
this.context.stdout.write("no audit entries\n");
|
|
44697
|
+
return 0;
|
|
44698
|
+
}
|
|
44699
|
+
this.context.stdout.write(
|
|
44700
|
+
`${table(
|
|
44701
|
+
["id", "ts", "actor", "agent", "event"],
|
|
44702
|
+
data.entries.map((e2) => [
|
|
44703
|
+
String(e2.id),
|
|
44704
|
+
e2.ts,
|
|
44705
|
+
e2.actor_user_id ?? "(none)",
|
|
44706
|
+
e2.actor_agent,
|
|
44707
|
+
e2.event_type
|
|
44708
|
+
])
|
|
44709
|
+
)}
|
|
44649
44710
|
`
|
|
44650
|
-
|
|
44651
|
-
|
|
44652
|
-
|
|
44711
|
+
);
|
|
44712
|
+
if (data.next_cursor) {
|
|
44713
|
+
this.context.stdout.write(`(next: --since-id ${data.next_cursor})
|
|
44653
44714
|
`);
|
|
44715
|
+
}
|
|
44716
|
+
return 0;
|
|
44717
|
+
} catch (err) {
|
|
44718
|
+
return handleExecError(this.context.stderr, err);
|
|
44654
44719
|
}
|
|
44655
|
-
return 0;
|
|
44656
44720
|
}
|
|
44657
44721
|
};
|
|
44658
44722
|
var AuditVerifyCommand = class extends Command {
|
|
@@ -44662,23 +44726,27 @@ var AuditVerifyCommand = class extends Command {
|
|
|
44662
44726
|
});
|
|
44663
44727
|
json = options_exports.Boolean("--json", false);
|
|
44664
44728
|
async execute() {
|
|
44665
|
-
|
|
44666
|
-
|
|
44667
|
-
|
|
44668
|
-
this.
|
|
44729
|
+
try {
|
|
44730
|
+
const client = new ApiClient();
|
|
44731
|
+
const data = await client.request("/v1/audit/verify", { method: "POST" });
|
|
44732
|
+
if (this.json) {
|
|
44733
|
+
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
44669
44734
|
`);
|
|
44670
|
-
|
|
44671
|
-
|
|
44672
|
-
|
|
44673
|
-
|
|
44735
|
+
return data.ok ? 0 : 1;
|
|
44736
|
+
}
|
|
44737
|
+
if (data.ok) {
|
|
44738
|
+
this.context.stdout.write(`OK: ${data.checked} entries verified
|
|
44674
44739
|
`);
|
|
44675
|
-
|
|
44676
|
-
|
|
44677
|
-
|
|
44678
|
-
|
|
44740
|
+
return 0;
|
|
44741
|
+
}
|
|
44742
|
+
this.context.stdout.write(
|
|
44743
|
+
`FAIL: chain broken at id ${data.broken_at_id} (${data.reason}); ${data.checked} entries verified before break
|
|
44679
44744
|
`
|
|
44680
|
-
|
|
44681
|
-
|
|
44745
|
+
);
|
|
44746
|
+
return 1;
|
|
44747
|
+
} catch (err) {
|
|
44748
|
+
return handleExecError(this.context.stderr, err);
|
|
44749
|
+
}
|
|
44682
44750
|
}
|
|
44683
44751
|
};
|
|
44684
44752
|
|
|
@@ -44719,11 +44787,10 @@ async function resolveAllAliases(client, argv2, extraStrings = []) {
|
|
|
44719
44787
|
return resolved;
|
|
44720
44788
|
}
|
|
44721
44789
|
function substitute(text, resolved) {
|
|
44790
|
+
const sorted = [...resolved].sort((a, b2) => b2.alias.literal.length - a.alias.literal.length);
|
|
44722
44791
|
let out = text;
|
|
44723
|
-
for (const r of
|
|
44724
|
-
|
|
44725
|
-
out = out.split(r.alias.literal).join(r.value);
|
|
44726
|
-
}
|
|
44792
|
+
for (const r of sorted) {
|
|
44793
|
+
out = out.split(r.alias.literal).join(r.value);
|
|
44727
44794
|
}
|
|
44728
44795
|
return out;
|
|
44729
44796
|
}
|
|
@@ -44826,10 +44893,18 @@ var BUILTIN_LINE_PATTERNS = BUILTIN_PATTERNS.filter((p2) => !p2.multiline);
|
|
|
44826
44893
|
|
|
44827
44894
|
// ../../packages/redactor/dist/entropy.js
|
|
44828
44895
|
var TOKEN_BOUNDARY_RE = /[\s,;:'"<>(){}[\]=]+/;
|
|
44896
|
+
var DEFAULT_EXCLUDE_PREFIXES = [
|
|
44897
|
+
"sha1-",
|
|
44898
|
+
"sha256:",
|
|
44899
|
+
"sha256-",
|
|
44900
|
+
"sha384-",
|
|
44901
|
+
"sha512-"
|
|
44902
|
+
];
|
|
44829
44903
|
var DEFAULTS = {
|
|
44830
44904
|
enabled: true,
|
|
44831
44905
|
minLength: 24,
|
|
44832
|
-
minBitsPerChar: 4.5
|
|
44906
|
+
minBitsPerChar: 4.5,
|
|
44907
|
+
excludePrefixes: DEFAULT_EXCLUDE_PREFIXES
|
|
44833
44908
|
};
|
|
44834
44909
|
function shannonEntropy(s) {
|
|
44835
44910
|
if (s.length === 0)
|
|
@@ -44862,9 +44937,13 @@ function findEntropyMatches(text, opts = {}) {
|
|
|
44862
44937
|
const end = i;
|
|
44863
44938
|
const token = text.slice(start, end);
|
|
44864
44939
|
if (token.length >= cfg.minLength) {
|
|
44865
|
-
const
|
|
44866
|
-
|
|
44867
|
-
|
|
44940
|
+
const lower = token.toLowerCase();
|
|
44941
|
+
const excluded = cfg.excludePrefixes.some((p2) => lower.startsWith(p2));
|
|
44942
|
+
if (!excluded) {
|
|
44943
|
+
const h2 = shannonEntropy(token);
|
|
44944
|
+
if (h2 >= cfg.minBitsPerChar) {
|
|
44945
|
+
matches.push({ start, end, token });
|
|
44946
|
+
}
|
|
44868
44947
|
}
|
|
44869
44948
|
}
|
|
44870
44949
|
}
|
|
@@ -45008,7 +45087,19 @@ var ENV_ALLOWLIST = [
|
|
|
45008
45087
|
"PWD",
|
|
45009
45088
|
"OLDPWD",
|
|
45010
45089
|
"TMPDIR",
|
|
45011
|
-
"SSH_AUTH_SOCK"
|
|
45090
|
+
"SSH_AUTH_SOCK",
|
|
45091
|
+
// Windows-specific
|
|
45092
|
+
"USERPROFILE",
|
|
45093
|
+
"USERNAME",
|
|
45094
|
+
"COMPUTERNAME",
|
|
45095
|
+
"TEMP",
|
|
45096
|
+
"TMP",
|
|
45097
|
+
"SYSTEMROOT",
|
|
45098
|
+
"SYSTEMDRIVE",
|
|
45099
|
+
"WINDIR",
|
|
45100
|
+
"COMSPEC",
|
|
45101
|
+
"APPDATA",
|
|
45102
|
+
"LOCALAPPDATA"
|
|
45012
45103
|
];
|
|
45013
45104
|
function spawnPrivileged(opts) {
|
|
45014
45105
|
const startedAt = Date.now();
|
|
@@ -45021,7 +45112,32 @@ function spawnPrivileged(opts) {
|
|
|
45021
45112
|
for (const [k2, v2] of Object.entries(opts.injectedEnv)) env2[k2] = v2;
|
|
45022
45113
|
}
|
|
45023
45114
|
const stdio = ["inherit", "pipe", "pipe"];
|
|
45024
|
-
const
|
|
45115
|
+
const WIN_BUILTINS = /* @__PURE__ */ new Set([
|
|
45116
|
+
"echo",
|
|
45117
|
+
"dir",
|
|
45118
|
+
"type",
|
|
45119
|
+
"copy",
|
|
45120
|
+
"del",
|
|
45121
|
+
"move",
|
|
45122
|
+
"ren",
|
|
45123
|
+
"md",
|
|
45124
|
+
"mkdir",
|
|
45125
|
+
"rd",
|
|
45126
|
+
"rmdir",
|
|
45127
|
+
"cd",
|
|
45128
|
+
"cls",
|
|
45129
|
+
"set",
|
|
45130
|
+
"pause",
|
|
45131
|
+
"find",
|
|
45132
|
+
"where"
|
|
45133
|
+
]);
|
|
45134
|
+
let spawnCmd = opts.command;
|
|
45135
|
+
let spawnArgs = opts.args;
|
|
45136
|
+
if (process.platform === "win32" && WIN_BUILTINS.has(opts.command.toLowerCase())) {
|
|
45137
|
+
spawnCmd = process.env.COMSPEC ?? "cmd.exe";
|
|
45138
|
+
spawnArgs = ["/d", "/s", "/c", opts.command, ...opts.args];
|
|
45139
|
+
}
|
|
45140
|
+
const child = spawn(spawnCmd, spawnArgs, {
|
|
45025
45141
|
env: env2,
|
|
45026
45142
|
stdio,
|
|
45027
45143
|
detached: false
|
|
@@ -45152,7 +45268,11 @@ spelled \`--from\` to avoid the collision.)
|
|
|
45152
45268
|
`);
|
|
45153
45269
|
return 2;
|
|
45154
45270
|
}
|
|
45155
|
-
|
|
45271
|
+
this.context.stderr.write(
|
|
45272
|
+
`keynv: unexpected error loading env file: ${err instanceof Error ? err.message : String(err)}
|
|
45273
|
+
`
|
|
45274
|
+
);
|
|
45275
|
+
return 2;
|
|
45156
45276
|
}
|
|
45157
45277
|
const viaEnvSpecs = [];
|
|
45158
45278
|
for (const spec of this.viaEnv ?? []) {
|
|
@@ -45224,11 +45344,18 @@ spelled \`--from\` to avoid the collision.)
|
|
|
45224
45344
|
injectedEnv[spec.name] = value;
|
|
45225
45345
|
}
|
|
45226
45346
|
if (envFileLoaded && !this.quiet) {
|
|
45227
|
-
const
|
|
45228
|
-
const
|
|
45347
|
+
const aliasEntries = envFileLoaded.entries.filter((e2) => e2.isAlias);
|
|
45348
|
+
const plainEntries = envFileLoaded.entries.filter((e2) => !e2.isAlias);
|
|
45349
|
+
const displayPath = relative(process.cwd(), envFileLoaded.path) || envFileLoaded.path;
|
|
45350
|
+
const parts = [];
|
|
45351
|
+
if (aliasEntries.length > 0) {
|
|
45352
|
+
parts.push(aliasEntries.map((e2) => `${e2.name}=${e2.value}`).join(", ") + " (vault)");
|
|
45353
|
+
}
|
|
45354
|
+
if (plainEntries.length > 0) {
|
|
45355
|
+
parts.push(plainEntries.map((e2) => e2.name).join(", ") + " (plain)");
|
|
45356
|
+
}
|
|
45229
45357
|
this.context.stderr.write(
|
|
45230
|
-
`keynv: loaded ${
|
|
45231
|
-
`
|
|
45358
|
+
`keynv: loaded ${displayPath}` + (parts.length > 0 ? ` \u2014 ${parts.join("; ")}` : "") + "\n"
|
|
45232
45359
|
);
|
|
45233
45360
|
}
|
|
45234
45361
|
const timeoutS = this.timeout ? Number.parseInt(this.timeout, 10) : void 0;
|
|
@@ -45268,10 +45395,158 @@ function signalNumber(sig) {
|
|
|
45268
45395
|
}
|
|
45269
45396
|
|
|
45270
45397
|
// src/commands/init.ts
|
|
45398
|
+
init_dist();
|
|
45271
45399
|
init_http();
|
|
45400
|
+
init_envFile();
|
|
45401
|
+
init_heuristics();
|
|
45272
45402
|
init_init();
|
|
45273
45403
|
init_cancel();
|
|
45274
45404
|
init_tty();
|
|
45405
|
+
|
|
45406
|
+
// src/commands/project.ts
|
|
45407
|
+
init_http();
|
|
45408
|
+
init_format();
|
|
45409
|
+
function isProjectId(input) {
|
|
45410
|
+
return input.startsWith("p_") || /^[a-f0-9]{20,}$/i.test(input);
|
|
45411
|
+
}
|
|
45412
|
+
async function resolveProjectId(client, input) {
|
|
45413
|
+
if (isProjectId(input)) {
|
|
45414
|
+
return input;
|
|
45415
|
+
}
|
|
45416
|
+
const data = await client.request("/v1/projects");
|
|
45417
|
+
const match = data.projects.find((p2) => p2.name === input);
|
|
45418
|
+
if (!match) throw new Error(`project not found: ${input}`);
|
|
45419
|
+
return match.id;
|
|
45420
|
+
}
|
|
45421
|
+
var ProjectListCommand = class extends Command {
|
|
45422
|
+
static paths = [["project", "list"]];
|
|
45423
|
+
static usage = Command.Usage({
|
|
45424
|
+
description: "List projects visible to the current user."
|
|
45425
|
+
});
|
|
45426
|
+
json = options_exports.Boolean("--json", false);
|
|
45427
|
+
async execute() {
|
|
45428
|
+
try {
|
|
45429
|
+
const client = new ApiClient();
|
|
45430
|
+
const data = await client.request("/v1/projects");
|
|
45431
|
+
if (this.json) {
|
|
45432
|
+
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
45433
|
+
`);
|
|
45434
|
+
return 0;
|
|
45435
|
+
}
|
|
45436
|
+
if (data.projects.length === 0) {
|
|
45437
|
+
this.context.stdout.write("no projects\n");
|
|
45438
|
+
return 0;
|
|
45439
|
+
}
|
|
45440
|
+
this.context.stdout.write(
|
|
45441
|
+
`${table(
|
|
45442
|
+
["name", "id", "created_at"],
|
|
45443
|
+
data.projects.map((p2) => [p2.name, p2.id, p2.created_at])
|
|
45444
|
+
)}
|
|
45445
|
+
`
|
|
45446
|
+
);
|
|
45447
|
+
return 0;
|
|
45448
|
+
} catch (err) {
|
|
45449
|
+
return handleExecError(this.context.stderr, err);
|
|
45450
|
+
}
|
|
45451
|
+
}
|
|
45452
|
+
};
|
|
45453
|
+
var ProjectCreateCommand = class extends Command {
|
|
45454
|
+
static paths = [["project", "create"]];
|
|
45455
|
+
static usage = Command.Usage({
|
|
45456
|
+
description: "Create a new project with one or more environments.",
|
|
45457
|
+
examples: [["Two envs", "$0 project create billing --env dev --env prod:production:approval"]]
|
|
45458
|
+
});
|
|
45459
|
+
name = options_exports.String();
|
|
45460
|
+
envs = options_exports.Array("--env", {
|
|
45461
|
+
description: 'Environment spec: name[:tier[:approval]]. Tier \u2208 {production,non-production}; "approval" sets require_approval=true.'
|
|
45462
|
+
});
|
|
45463
|
+
async execute() {
|
|
45464
|
+
try {
|
|
45465
|
+
const envs = (this.envs ?? ["dev"]).map((spec) => {
|
|
45466
|
+
const [name, tier, approval] = spec.split(":");
|
|
45467
|
+
return {
|
|
45468
|
+
name: name ?? "",
|
|
45469
|
+
tier: tier ?? "non-production",
|
|
45470
|
+
require_approval: approval === "approval"
|
|
45471
|
+
};
|
|
45472
|
+
});
|
|
45473
|
+
const client = new ApiClient();
|
|
45474
|
+
const result = await client.request("/v1/projects", {
|
|
45475
|
+
method: "POST",
|
|
45476
|
+
body: { name: this.name, environments: envs }
|
|
45477
|
+
});
|
|
45478
|
+
this.context.stdout.write(`created project ${result.name} (${result.id})
|
|
45479
|
+
`);
|
|
45480
|
+
for (const e2 of envs) {
|
|
45481
|
+
this.context.stdout.write(
|
|
45482
|
+
` env: ${e2.name} (tier=${e2.tier}, approval=${e2.require_approval})
|
|
45483
|
+
`
|
|
45484
|
+
);
|
|
45485
|
+
}
|
|
45486
|
+
return 0;
|
|
45487
|
+
} catch (err) {
|
|
45488
|
+
return handleExecError(this.context.stderr, err);
|
|
45489
|
+
}
|
|
45490
|
+
}
|
|
45491
|
+
};
|
|
45492
|
+
var ProjectDescribeCommand = class extends Command {
|
|
45493
|
+
static paths = [["project", "describe"]];
|
|
45494
|
+
static usage = Command.Usage({
|
|
45495
|
+
description: "Show metadata for one project.",
|
|
45496
|
+
examples: [
|
|
45497
|
+
["By ID", "$0 project describe p_go6rqgwz0wlokdsl55ikn"],
|
|
45498
|
+
["By name", "$0 project describe myproject"]
|
|
45499
|
+
]
|
|
45500
|
+
});
|
|
45501
|
+
id = options_exports.String();
|
|
45502
|
+
json = options_exports.Boolean("--json", false);
|
|
45503
|
+
async execute() {
|
|
45504
|
+
try {
|
|
45505
|
+
const client = new ApiClient();
|
|
45506
|
+
const projectId2 = await resolveProjectId(client, this.id);
|
|
45507
|
+
const data = await client.request(`/v1/projects/${projectId2}`);
|
|
45508
|
+
if (this.json) {
|
|
45509
|
+
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
45510
|
+
`);
|
|
45511
|
+
return 0;
|
|
45512
|
+
}
|
|
45513
|
+
this.context.stdout.write(`project: ${data.name} (${data.id})
|
|
45514
|
+
`);
|
|
45515
|
+
for (const e2 of data.environments) {
|
|
45516
|
+
this.context.stdout.write(
|
|
45517
|
+
` env: ${e2.name} (tier=${e2.tier}, approval=${e2.require_approval})
|
|
45518
|
+
`
|
|
45519
|
+
);
|
|
45520
|
+
}
|
|
45521
|
+
return 0;
|
|
45522
|
+
} catch (err) {
|
|
45523
|
+
return handleExecError(this.context.stderr, err);
|
|
45524
|
+
}
|
|
45525
|
+
}
|
|
45526
|
+
};
|
|
45527
|
+
var ProjectDeleteCommand = class extends Command {
|
|
45528
|
+
static paths = [["project", "delete"]];
|
|
45529
|
+
static usage = Command.Usage({ description: "Soft-delete a project." });
|
|
45530
|
+
id = options_exports.String();
|
|
45531
|
+
force = options_exports.Boolean("--force", false);
|
|
45532
|
+
async execute() {
|
|
45533
|
+
if (!this.force) {
|
|
45534
|
+
this.context.stderr.write("keynv: refusing to delete without --force\n");
|
|
45535
|
+
return 2;
|
|
45536
|
+
}
|
|
45537
|
+
try {
|
|
45538
|
+
const client = new ApiClient();
|
|
45539
|
+
await client.request(`/v1/projects/${this.id}`, { method: "DELETE" });
|
|
45540
|
+
this.context.stdout.write(`deleted project ${this.id}
|
|
45541
|
+
`);
|
|
45542
|
+
return 0;
|
|
45543
|
+
} catch (err) {
|
|
45544
|
+
return handleExecError(this.context.stderr, err);
|
|
45545
|
+
}
|
|
45546
|
+
}
|
|
45547
|
+
};
|
|
45548
|
+
|
|
45549
|
+
// src/commands/init.ts
|
|
45275
45550
|
var InitCommand = class extends Command {
|
|
45276
45551
|
static paths = [["init"]];
|
|
45277
45552
|
static usage = Command.Usage({
|
|
@@ -45285,6 +45560,11 @@ package.json scripts with \`keynv exec\`.
|
|
|
45285
45560
|
Safe to re-run: existing .keynv.env entries are preserved; new
|
|
45286
45561
|
entries are appended below a marker.
|
|
45287
45562
|
|
|
45563
|
+
--dry-run prints the secrets that would be uploaded and the alias
|
|
45564
|
+
mappings that would be written to .keynv.env, then exits without
|
|
45565
|
+
touching any files or making any network calls. Use it to preview
|
|
45566
|
+
what init will do before committing.
|
|
45567
|
+
|
|
45288
45568
|
Requires an interactive terminal (clack TUI). For scripted
|
|
45289
45569
|
migration, use the lower-level \`keynv project\` and \`keynv secret\`
|
|
45290
45570
|
commands directly.
|
|
@@ -45292,28 +45572,47 @@ commands directly.
|
|
|
45292
45572
|
examples: [
|
|
45293
45573
|
["Walk the current project", "$0 init"],
|
|
45294
45574
|
["Preview without writing or uploading", "$0 init --dry-run"],
|
|
45295
|
-
["Skip the package.json script-wrapping step", "$0 init --no-scripts"]
|
|
45575
|
+
["Skip the package.json script-wrapping step", "$0 init --no-scripts"],
|
|
45576
|
+
["Non-interactive (CI/CD)", "$0 init --env-file .env --project myproject --env dev"]
|
|
45296
45577
|
]
|
|
45297
45578
|
});
|
|
45298
45579
|
dryRun = options_exports.Boolean("--dry-run", false, {
|
|
45299
|
-
description: "
|
|
45580
|
+
description: "Scan .env files and print the secrets that would be uploaded and the alias mappings that would be written \u2014 no files written, no vault calls made."
|
|
45300
45581
|
});
|
|
45301
45582
|
noScripts = options_exports.Boolean("--no-scripts", false, {
|
|
45302
45583
|
description: "Skip the package.json script-wrapping step."
|
|
45303
45584
|
});
|
|
45585
|
+
envFile = options_exports.String("--env-file", {
|
|
45586
|
+
description: "Path to .env file to migrate (non-interactive mode)."
|
|
45587
|
+
});
|
|
45588
|
+
project = options_exports.String("--project", {
|
|
45589
|
+
description: "Project name or ID (non-interactive mode)."
|
|
45590
|
+
});
|
|
45591
|
+
env = options_exports.String("--env", {
|
|
45592
|
+
description: "Environment name for --env-file (default: dev)."
|
|
45593
|
+
});
|
|
45594
|
+
secret = options_exports.Array("--secret", {
|
|
45595
|
+
description: "KEY=value secret to upload (non-interactive). Can be specified multiple times."
|
|
45596
|
+
});
|
|
45304
45597
|
async execute() {
|
|
45305
|
-
if (!isInteractive()) {
|
|
45306
|
-
this.context.stderr.write(
|
|
45307
|
-
"keynv init requires an interactive terminal. Use the lower-level commands (`keynv project`, `keynv secret`) for scripted setup.\n"
|
|
45308
|
-
);
|
|
45309
|
-
return 1;
|
|
45310
|
-
}
|
|
45311
45598
|
const client = new ApiClient();
|
|
45312
45599
|
await client.ensureHydrated();
|
|
45313
45600
|
if (!client.isLoggedIn) {
|
|
45314
45601
|
this.context.stderr.write("keynv: not logged in. Run `keynv login` first.\n");
|
|
45315
45602
|
return 1;
|
|
45316
45603
|
}
|
|
45604
|
+
const hasEnvFile = this.envFile != null;
|
|
45605
|
+
const hasSecrets = this.secret != null && this.secret.length > 0;
|
|
45606
|
+
const isNonInteractive = hasEnvFile || hasSecrets;
|
|
45607
|
+
if (isNonInteractive) {
|
|
45608
|
+
return this.runNonInteractive(client);
|
|
45609
|
+
}
|
|
45610
|
+
if (!isInteractive()) {
|
|
45611
|
+
this.context.stderr.write(
|
|
45612
|
+
"keynv init requires an interactive terminal. Use --env-file or --secret for scripted setup.\n"
|
|
45613
|
+
);
|
|
45614
|
+
return 1;
|
|
45615
|
+
}
|
|
45317
45616
|
try {
|
|
45318
45617
|
const outcome = await runInitFlow(client, {
|
|
45319
45618
|
cwd: process.cwd(),
|
|
@@ -45329,6 +45628,97 @@ commands directly.
|
|
|
45329
45628
|
return 1;
|
|
45330
45629
|
}
|
|
45331
45630
|
}
|
|
45631
|
+
async runNonInteractive(client) {
|
|
45632
|
+
const projectName = this.project;
|
|
45633
|
+
if (!projectName) {
|
|
45634
|
+
this.context.stderr.write("keynv: --project is required in non-interactive mode.\n");
|
|
45635
|
+
return 1;
|
|
45636
|
+
}
|
|
45637
|
+
const resolved = await resolveProjectId(client, projectName);
|
|
45638
|
+
if (!resolved) {
|
|
45639
|
+
this.context.stderr.write(`keynv: project not found: ${projectName}
|
|
45640
|
+
`);
|
|
45641
|
+
return 1;
|
|
45642
|
+
}
|
|
45643
|
+
const projectId2 = resolved;
|
|
45644
|
+
const envName = this.env ?? "dev";
|
|
45645
|
+
const secrets = [];
|
|
45646
|
+
if (this.envFile) {
|
|
45647
|
+
try {
|
|
45648
|
+
const content = readFileSync(this.envFile, "utf8");
|
|
45649
|
+
const entries = parseEnvFile(content, this.envFile);
|
|
45650
|
+
for (const e2 of entries) {
|
|
45651
|
+
if (classifyEntry(e2.name, e2.value).verdict === "secret") {
|
|
45652
|
+
secrets.push({ name: e2.name, value: e2.value });
|
|
45653
|
+
}
|
|
45654
|
+
}
|
|
45655
|
+
} catch (err) {
|
|
45656
|
+
this.context.stderr.write(
|
|
45657
|
+
`keynv: cannot read ${this.envFile}: ${err instanceof Error ? err.message : String(err)}
|
|
45658
|
+
`
|
|
45659
|
+
);
|
|
45660
|
+
return 1;
|
|
45661
|
+
}
|
|
45662
|
+
}
|
|
45663
|
+
if (this.secret) {
|
|
45664
|
+
for (const spec of this.secret) {
|
|
45665
|
+
const eq = spec.indexOf("=");
|
|
45666
|
+
if (eq <= 0) {
|
|
45667
|
+
this.context.stderr.write(`keynv: invalid --secret '${spec}', expected KEY=value
|
|
45668
|
+
`);
|
|
45669
|
+
return 1;
|
|
45670
|
+
}
|
|
45671
|
+
const name = spec.slice(0, eq);
|
|
45672
|
+
const value = spec.slice(eq + 1);
|
|
45673
|
+
secrets.push({ name, value });
|
|
45674
|
+
}
|
|
45675
|
+
}
|
|
45676
|
+
if (secrets.length === 0) {
|
|
45677
|
+
this.context.stdout.write("keynv: nothing to migrate.\n");
|
|
45678
|
+
return 0;
|
|
45679
|
+
}
|
|
45680
|
+
if (this.dryRun) {
|
|
45681
|
+
this.context.stdout.write(
|
|
45682
|
+
`keynv: dry-run \u2014 would upload ${secrets.length} secret(s) to project ${projectName} (${projectId2}) in env ${envName}
|
|
45683
|
+
`
|
|
45684
|
+
);
|
|
45685
|
+
for (const { name } of secrets) {
|
|
45686
|
+
const aliasKey = name.toLowerCase().replace(/_/g, "-");
|
|
45687
|
+
this.context.stdout.write(` ${name}=@${projectName}.${envName}.${aliasKey}
|
|
45688
|
+
`);
|
|
45689
|
+
}
|
|
45690
|
+
return 0;
|
|
45691
|
+
}
|
|
45692
|
+
let uploaded = 0;
|
|
45693
|
+
const failed = [];
|
|
45694
|
+
for (const { name, value } of secrets) {
|
|
45695
|
+
const aliasKey = name.toLowerCase().replace(/_/g, "-");
|
|
45696
|
+
const alias2 = buildAlias({ project: projectName, environment: envName, key: aliasKey });
|
|
45697
|
+
if (!alias2) {
|
|
45698
|
+
failed.push(`${name} (invalid alias key: ${aliasKey})`);
|
|
45699
|
+
continue;
|
|
45700
|
+
}
|
|
45701
|
+
try {
|
|
45702
|
+
await client.request(`/v1/projects/${projectId2}/secrets`, {
|
|
45703
|
+
method: "POST",
|
|
45704
|
+
body: { env: envName, key: aliasKey, value }
|
|
45705
|
+
});
|
|
45706
|
+
uploaded++;
|
|
45707
|
+
} catch (err) {
|
|
45708
|
+
failed.push(`${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
45709
|
+
}
|
|
45710
|
+
}
|
|
45711
|
+
this.context.stdout.write(
|
|
45712
|
+
`keynv: uploaded ${uploaded}/${secrets.length} secret(s) to ${projectName}.${envName}
|
|
45713
|
+
`
|
|
45714
|
+
);
|
|
45715
|
+
if (failed.length > 0) {
|
|
45716
|
+
for (const f of failed) this.context.stderr.write(` failed: ${f}
|
|
45717
|
+
`);
|
|
45718
|
+
return 1;
|
|
45719
|
+
}
|
|
45720
|
+
return 0;
|
|
45721
|
+
}
|
|
45332
45722
|
};
|
|
45333
45723
|
|
|
45334
45724
|
// src/commands/login.ts
|
|
@@ -45408,6 +45798,13 @@ var LoginCommand = class extends Command {
|
|
|
45408
45798
|
});
|
|
45409
45799
|
async execute() {
|
|
45410
45800
|
const finalServerUrl = this.server ?? DEFAULT_SERVER_URL;
|
|
45801
|
+
try {
|
|
45802
|
+
new URL(finalServerUrl);
|
|
45803
|
+
} catch {
|
|
45804
|
+
this.context.stderr.write(`keynv: invalid server URL '${finalServerUrl}'
|
|
45805
|
+
`);
|
|
45806
|
+
return 1;
|
|
45807
|
+
}
|
|
45411
45808
|
if (this.token) {
|
|
45412
45809
|
return this.loginWithToken(finalServerUrl, this.token);
|
|
45413
45810
|
}
|
|
@@ -45430,11 +45827,20 @@ var LoginCommand = class extends Command {
|
|
|
45430
45827
|
}
|
|
45431
45828
|
const email2 = this.email ?? await promptLine("email: ");
|
|
45432
45829
|
const password = this.password ?? await promptHidden("password: ");
|
|
45433
|
-
|
|
45434
|
-
|
|
45435
|
-
|
|
45436
|
-
|
|
45437
|
-
|
|
45830
|
+
let res;
|
|
45831
|
+
try {
|
|
45832
|
+
res = await fetch(new URL("/v1/auth/login", finalServerUrl).toString(), {
|
|
45833
|
+
method: "POST",
|
|
45834
|
+
headers: { "content-type": "application/json", "x-keynv-agent": AGENT },
|
|
45835
|
+
body: JSON.stringify({ email: email2, password })
|
|
45836
|
+
});
|
|
45837
|
+
} catch (err) {
|
|
45838
|
+
this.context.stderr.write(
|
|
45839
|
+
`keynv: cannot reach server '${finalServerUrl}' \u2014 ${err instanceof Error ? err.message : String(err)}
|
|
45840
|
+
`
|
|
45841
|
+
);
|
|
45842
|
+
return 1;
|
|
45843
|
+
}
|
|
45438
45844
|
if (!res.ok) {
|
|
45439
45845
|
const text = await res.text();
|
|
45440
45846
|
let msg = `login failed (${res.status})`;
|
|
@@ -45480,9 +45886,18 @@ var LoginCommand = class extends Command {
|
|
|
45480
45886
|
return 0;
|
|
45481
45887
|
}
|
|
45482
45888
|
async loginWithToken(serverUrl, token) {
|
|
45483
|
-
|
|
45484
|
-
|
|
45485
|
-
|
|
45889
|
+
let res;
|
|
45890
|
+
try {
|
|
45891
|
+
res = await fetch(new URL("/v1/whoami", serverUrl).toString(), {
|
|
45892
|
+
headers: { authorization: `Bearer ${token}`, "x-keynv-agent": AGENT }
|
|
45893
|
+
});
|
|
45894
|
+
} catch (err) {
|
|
45895
|
+
this.context.stderr.write(
|
|
45896
|
+
`keynv: cannot reach server '${serverUrl}' \u2014 ${err instanceof Error ? err.message : String(err)}
|
|
45897
|
+
`
|
|
45898
|
+
);
|
|
45899
|
+
return 1;
|
|
45900
|
+
}
|
|
45486
45901
|
if (!res.ok) {
|
|
45487
45902
|
const text = await res.text();
|
|
45488
45903
|
let msg = `token login failed (${res.status})`;
|
|
@@ -45560,48 +45975,46 @@ var WhoamiCommand = class extends Command {
|
|
|
45560
45975
|
});
|
|
45561
45976
|
json = options_exports.Boolean("--json", false);
|
|
45562
45977
|
async execute() {
|
|
45563
|
-
|
|
45564
|
-
|
|
45565
|
-
|
|
45566
|
-
|
|
45567
|
-
|
|
45568
|
-
|
|
45569
|
-
|
|
45570
|
-
|
|
45571
|
-
this.
|
|
45978
|
+
try {
|
|
45979
|
+
const client = new ApiClient();
|
|
45980
|
+
await client.ensureHydrated();
|
|
45981
|
+
if (!client.isLoggedIn) {
|
|
45982
|
+
this.context.stderr.write("keynv: not logged in. Run `keynv login`.\n");
|
|
45983
|
+
return 1;
|
|
45984
|
+
}
|
|
45985
|
+
const data = await client.request("/v1/whoami");
|
|
45986
|
+
if (this.json) {
|
|
45987
|
+
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
45572
45988
|
`);
|
|
45573
|
-
|
|
45574
|
-
|
|
45575
|
-
|
|
45989
|
+
return 0;
|
|
45990
|
+
}
|
|
45991
|
+
this.context.stdout.write(`user: ${data.email} (${data.id})
|
|
45576
45992
|
`);
|
|
45577
|
-
|
|
45993
|
+
const orgDisplay = data.org_name ? `${data.org_name} (${data.org_id})` : data.org_id;
|
|
45994
|
+
this.context.stdout.write(`org: ${orgDisplay}
|
|
45578
45995
|
`);
|
|
45579
|
-
|
|
45996
|
+
this.context.stdout.write(`role: ${data.org_role}
|
|
45580
45997
|
`);
|
|
45581
|
-
|
|
45582
|
-
|
|
45583
|
-
|
|
45584
|
-
|
|
45585
|
-
|
|
45586
|
-
|
|
45998
|
+
if (data.memberships.length === 0) {
|
|
45999
|
+
this.context.stdout.write("memberships: (none)\n");
|
|
46000
|
+
} else {
|
|
46001
|
+
this.context.stdout.write("memberships:\n");
|
|
46002
|
+
for (const m of data.memberships) {
|
|
46003
|
+
const proj = m.project_name ? `${m.project_name} (${m.project_id})` : m.project_id;
|
|
46004
|
+
this.context.stdout.write(` ${proj}: ${m.role}
|
|
45587
46005
|
`);
|
|
46006
|
+
}
|
|
45588
46007
|
}
|
|
46008
|
+
return 0;
|
|
46009
|
+
} catch (err) {
|
|
46010
|
+
return handleExecError(this.context.stderr, err);
|
|
45589
46011
|
}
|
|
45590
|
-
return 0;
|
|
45591
46012
|
}
|
|
45592
46013
|
};
|
|
45593
46014
|
|
|
45594
46015
|
// src/commands/member.ts
|
|
45595
46016
|
init_http();
|
|
45596
46017
|
init_format();
|
|
45597
|
-
async function findProjectIdByName(client, name) {
|
|
45598
|
-
const data = await client.request(
|
|
45599
|
-
"/v1/projects"
|
|
45600
|
-
);
|
|
45601
|
-
const match = data.projects.find((p2) => p2.name === name);
|
|
45602
|
-
if (!match) throw new Error(`unknown project: ${name}`);
|
|
45603
|
-
return match.id;
|
|
45604
|
-
}
|
|
45605
46018
|
var MemberAddCommand = class extends Command {
|
|
45606
46019
|
static paths = [["member", "add"]];
|
|
45607
46020
|
static usage = Command.Usage({
|
|
@@ -45617,15 +46030,19 @@ var MemberAddCommand = class extends Command {
|
|
|
45617
46030
|
this.context.stderr.write("keynv: --role must be one of lead, developer, reader\n");
|
|
45618
46031
|
return 1;
|
|
45619
46032
|
}
|
|
45620
|
-
|
|
45621
|
-
|
|
45622
|
-
|
|
45623
|
-
|
|
45624
|
-
|
|
45625
|
-
|
|
45626
|
-
|
|
46033
|
+
try {
|
|
46034
|
+
const client = new ApiClient();
|
|
46035
|
+
const projectId2 = await resolveProjectId(client, this.project);
|
|
46036
|
+
await client.request(`/v1/projects/${projectId2}/members`, {
|
|
46037
|
+
method: "POST",
|
|
46038
|
+
body: { email: this.email, role: role2 }
|
|
46039
|
+
});
|
|
46040
|
+
this.context.stdout.write(`added ${this.email} to ${this.project} as ${role2}
|
|
45627
46041
|
`);
|
|
45628
|
-
|
|
46042
|
+
return 0;
|
|
46043
|
+
} catch (err) {
|
|
46044
|
+
return handleExecError(this.context.stderr, err);
|
|
46045
|
+
}
|
|
45629
46046
|
}
|
|
45630
46047
|
};
|
|
45631
46048
|
var MemberRemoveCommand = class extends Command {
|
|
@@ -45634,21 +46051,25 @@ var MemberRemoveCommand = class extends Command {
|
|
|
45634
46051
|
project = options_exports.String();
|
|
45635
46052
|
email = options_exports.String();
|
|
45636
46053
|
async execute() {
|
|
45637
|
-
|
|
45638
|
-
|
|
45639
|
-
|
|
45640
|
-
|
|
45641
|
-
|
|
45642
|
-
|
|
46054
|
+
try {
|
|
46055
|
+
const client = new ApiClient();
|
|
46056
|
+
const projectId2 = await resolveProjectId(client, this.project);
|
|
46057
|
+
const members = await client.request(`/v1/projects/${projectId2}/members`);
|
|
46058
|
+
const target = members.members.find((m) => m.email === this.email);
|
|
46059
|
+
if (!target) {
|
|
46060
|
+
this.context.stderr.write(`keynv: ${this.email} is not a member of ${this.project}
|
|
45643
46061
|
`);
|
|
45644
|
-
|
|
45645
|
-
|
|
45646
|
-
|
|
45647
|
-
|
|
45648
|
-
|
|
45649
|
-
|
|
46062
|
+
return 1;
|
|
46063
|
+
}
|
|
46064
|
+
await client.request(`/v1/projects/${projectId2}/members/${target.user_id}`, {
|
|
46065
|
+
method: "DELETE"
|
|
46066
|
+
});
|
|
46067
|
+
this.context.stdout.write(`removed ${this.email} from ${this.project}
|
|
45650
46068
|
`);
|
|
45651
|
-
|
|
46069
|
+
return 0;
|
|
46070
|
+
} catch (err) {
|
|
46071
|
+
return handleExecError(this.context.stderr, err);
|
|
46072
|
+
}
|
|
45652
46073
|
}
|
|
45653
46074
|
};
|
|
45654
46075
|
var MemberListCommand = class extends Command {
|
|
@@ -45657,134 +46078,30 @@ var MemberListCommand = class extends Command {
|
|
|
45657
46078
|
project = options_exports.String();
|
|
45658
46079
|
json = options_exports.Boolean("--json", false);
|
|
45659
46080
|
async execute() {
|
|
45660
|
-
|
|
45661
|
-
|
|
45662
|
-
|
|
45663
|
-
|
|
45664
|
-
this.
|
|
45665
|
-
|
|
45666
|
-
return 0;
|
|
45667
|
-
}
|
|
45668
|
-
if (data.members.length === 0) {
|
|
45669
|
-
this.context.stdout.write("no members\n");
|
|
45670
|
-
return 0;
|
|
45671
|
-
}
|
|
45672
|
-
this.context.stdout.write(
|
|
45673
|
-
`${table(
|
|
45674
|
-
["email", "role", "granted_at"],
|
|
45675
|
-
data.members.map((m) => [m.email, m.role, m.granted_at])
|
|
45676
|
-
)}
|
|
45677
|
-
`
|
|
45678
|
-
);
|
|
45679
|
-
return 0;
|
|
45680
|
-
}
|
|
45681
|
-
};
|
|
45682
|
-
|
|
45683
|
-
// src/commands/project.ts
|
|
45684
|
-
init_http();
|
|
45685
|
-
init_format();
|
|
45686
|
-
var ProjectListCommand = class extends Command {
|
|
45687
|
-
static paths = [["project", "list"]];
|
|
45688
|
-
static usage = Command.Usage({
|
|
45689
|
-
description: "List projects visible to the current user."
|
|
45690
|
-
});
|
|
45691
|
-
json = options_exports.Boolean("--json", false);
|
|
45692
|
-
async execute() {
|
|
45693
|
-
const client = new ApiClient();
|
|
45694
|
-
const data = await client.request("/v1/projects");
|
|
45695
|
-
if (this.json) {
|
|
45696
|
-
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
45697
|
-
`);
|
|
45698
|
-
return 0;
|
|
45699
|
-
}
|
|
45700
|
-
if (data.projects.length === 0) {
|
|
45701
|
-
this.context.stdout.write("no projects\n");
|
|
45702
|
-
return 0;
|
|
45703
|
-
}
|
|
45704
|
-
this.context.stdout.write(
|
|
45705
|
-
`${table(
|
|
45706
|
-
["name", "id", "created_at"],
|
|
45707
|
-
data.projects.map((p2) => [p2.name, p2.id, p2.created_at])
|
|
45708
|
-
)}
|
|
45709
|
-
`
|
|
45710
|
-
);
|
|
45711
|
-
return 0;
|
|
45712
|
-
}
|
|
45713
|
-
};
|
|
45714
|
-
var ProjectCreateCommand = class extends Command {
|
|
45715
|
-
static paths = [["project", "create"]];
|
|
45716
|
-
static usage = Command.Usage({
|
|
45717
|
-
description: "Create a new project with one or more environments.",
|
|
45718
|
-
examples: [["Two envs", "$0 project create billing --env dev --env prod:production:approval"]]
|
|
45719
|
-
});
|
|
45720
|
-
name = options_exports.String();
|
|
45721
|
-
envs = options_exports.Array("--env", {
|
|
45722
|
-
description: 'Environment spec: name[:tier[:approval]]. Tier \u2208 {production,non-production}; "approval" sets require_approval=true.'
|
|
45723
|
-
});
|
|
45724
|
-
async execute() {
|
|
45725
|
-
const envs = (this.envs ?? ["dev"]).map((spec) => {
|
|
45726
|
-
const [name, tier, approval] = spec.split(":");
|
|
45727
|
-
return {
|
|
45728
|
-
name: name ?? "",
|
|
45729
|
-
tier: tier ?? "non-production",
|
|
45730
|
-
require_approval: approval === "approval"
|
|
45731
|
-
};
|
|
45732
|
-
});
|
|
45733
|
-
const client = new ApiClient();
|
|
45734
|
-
const result = await client.request("/v1/projects", {
|
|
45735
|
-
method: "POST",
|
|
45736
|
-
body: { name: this.name, environments: envs }
|
|
45737
|
-
});
|
|
45738
|
-
this.context.stdout.write(`created project ${result.name} (${result.id})
|
|
46081
|
+
try {
|
|
46082
|
+
const client = new ApiClient();
|
|
46083
|
+
const projectId2 = await resolveProjectId(client, this.project);
|
|
46084
|
+
const data = await client.request(`/v1/projects/${projectId2}/members`);
|
|
46085
|
+
if (this.json) {
|
|
46086
|
+
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
45739
46087
|
`);
|
|
45740
|
-
|
|
46088
|
+
return 0;
|
|
46089
|
+
}
|
|
46090
|
+
if (data.members.length === 0) {
|
|
46091
|
+
this.context.stdout.write("no members\n");
|
|
46092
|
+
return 0;
|
|
46093
|
+
}
|
|
45741
46094
|
this.context.stdout.write(
|
|
45742
|
-
|
|
46095
|
+
`${table(
|
|
46096
|
+
["email", "role", "granted_at"],
|
|
46097
|
+
data.members.map((m) => [m.email, m.role, m.granted_at])
|
|
46098
|
+
)}
|
|
45743
46099
|
`
|
|
45744
46100
|
);
|
|
45745
|
-
}
|
|
45746
|
-
return 0;
|
|
45747
|
-
}
|
|
45748
|
-
};
|
|
45749
|
-
var ProjectDescribeCommand = class extends Command {
|
|
45750
|
-
static paths = [["project", "describe"]];
|
|
45751
|
-
static usage = Command.Usage({ description: "Show metadata for one project." });
|
|
45752
|
-
id = options_exports.String();
|
|
45753
|
-
json = options_exports.Boolean("--json", false);
|
|
45754
|
-
async execute() {
|
|
45755
|
-
const client = new ApiClient();
|
|
45756
|
-
const data = await client.request(`/v1/projects/${this.id}`);
|
|
45757
|
-
if (this.json) {
|
|
45758
|
-
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
45759
|
-
`);
|
|
45760
46101
|
return 0;
|
|
46102
|
+
} catch (err) {
|
|
46103
|
+
return handleExecError(this.context.stderr, err);
|
|
45761
46104
|
}
|
|
45762
|
-
this.context.stdout.write(`project: ${data.name} (${data.id})
|
|
45763
|
-
`);
|
|
45764
|
-
for (const e2 of data.environments) {
|
|
45765
|
-
this.context.stdout.write(
|
|
45766
|
-
` env: ${e2.name} (tier=${e2.tier}, approval=${e2.require_approval})
|
|
45767
|
-
`
|
|
45768
|
-
);
|
|
45769
|
-
}
|
|
45770
|
-
return 0;
|
|
45771
|
-
}
|
|
45772
|
-
};
|
|
45773
|
-
var ProjectDeleteCommand = class extends Command {
|
|
45774
|
-
static paths = [["project", "delete"]];
|
|
45775
|
-
static usage = Command.Usage({ description: "Soft-delete a project." });
|
|
45776
|
-
id = options_exports.String();
|
|
45777
|
-
force = options_exports.Boolean("--force", false);
|
|
45778
|
-
async execute() {
|
|
45779
|
-
if (!this.force) {
|
|
45780
|
-
this.context.stderr.write("keynv: refusing to delete without --force\n");
|
|
45781
|
-
return 2;
|
|
45782
|
-
}
|
|
45783
|
-
const client = new ApiClient();
|
|
45784
|
-
await client.request(`/v1/projects/${this.id}`, { method: "DELETE" });
|
|
45785
|
-
this.context.stdout.write(`deleted project ${this.id}
|
|
45786
|
-
`);
|
|
45787
|
-
return 0;
|
|
45788
46105
|
}
|
|
45789
46106
|
};
|
|
45790
46107
|
var RedactCommand = class extends Command {
|
|
@@ -45829,11 +46146,9 @@ var RedactStreamCommand = class extends Command {
|
|
|
45829
46146
|
static usage = Command.Usage({
|
|
45830
46147
|
description: "Stream stdin through the line-buffered redactor to stdout.",
|
|
45831
46148
|
details: `
|
|
45832
|
-
|
|
45833
|
-
|
|
45834
|
-
|
|
45835
|
-
line structure. Multi-line patterns are NOT applied here (see the
|
|
45836
|
-
streaming-mode limitation in @keynv/redactor).
|
|
46149
|
+
Pipe any output through the line-buffered redactor to stdout.
|
|
46150
|
+
Preserves original line structure. Multi-line patterns are NOT applied
|
|
46151
|
+
here (see the streaming-mode limitation in the redactor package).
|
|
45837
46152
|
`
|
|
45838
46153
|
});
|
|
45839
46154
|
async execute() {
|
|
@@ -45854,12 +46169,7 @@ init_cancel();
|
|
|
45854
46169
|
init_pickProject();
|
|
45855
46170
|
init_pickSecret();
|
|
45856
46171
|
init_tty();
|
|
45857
|
-
|
|
45858
|
-
const data = await client.request("/v1/projects");
|
|
45859
|
-
const match = data.projects.find((p2) => p2.name === name);
|
|
45860
|
-
if (!match) throw new Error(`unknown project: ${name}`);
|
|
45861
|
-
return match.id;
|
|
45862
|
-
}
|
|
46172
|
+
var ALIAS_FORMAT_HINT = "Format: @<project>.<env>.<KEY> (project/env: lowercase kebab-case; key: letters, digits, _ or -)";
|
|
45863
46173
|
function missingAlias(stderr) {
|
|
45864
46174
|
stderr.write("keynv: missing <alias> (TTY required for interactive prompt).\n");
|
|
45865
46175
|
return 1;
|
|
@@ -45890,45 +46200,50 @@ var SecretCreateCommand = class extends Command {
|
|
|
45890
46200
|
});
|
|
45891
46201
|
stdin = options_exports.Boolean("--stdin", false);
|
|
45892
46202
|
async execute() {
|
|
45893
|
-
|
|
45894
|
-
|
|
45895
|
-
|
|
45896
|
-
|
|
45897
|
-
|
|
45898
|
-
if (!
|
|
45899
|
-
|
|
45900
|
-
|
|
45901
|
-
|
|
45902
|
-
|
|
45903
|
-
|
|
45904
|
-
|
|
45905
|
-
|
|
45906
|
-
|
|
46203
|
+
try {
|
|
46204
|
+
const client = new ApiClient();
|
|
46205
|
+
await client.ensureHydrated();
|
|
46206
|
+
let alias2 = this.alias;
|
|
46207
|
+
let value = this.value;
|
|
46208
|
+
if (!alias2) {
|
|
46209
|
+
if (!isInteractive()) return missingAlias(this.context.stderr);
|
|
46210
|
+
try {
|
|
46211
|
+
const built = await promptNewSecret(client);
|
|
46212
|
+
if (!built) return 1;
|
|
46213
|
+
alias2 = built.alias;
|
|
46214
|
+
value = built.value;
|
|
46215
|
+
} catch (err) {
|
|
46216
|
+
if (err instanceof UserCancelled) return 130;
|
|
46217
|
+
throw err;
|
|
46218
|
+
}
|
|
45907
46219
|
}
|
|
45908
|
-
|
|
45909
|
-
|
|
45910
|
-
|
|
45911
|
-
|
|
46220
|
+
const parsed = parseAlias(alias2);
|
|
46221
|
+
if (!parsed) {
|
|
46222
|
+
this.context.stderr.write(`keynv: invalid alias '${alias2}'.
|
|
46223
|
+
${ALIAS_FORMAT_HINT}
|
|
45912
46224
|
`);
|
|
45913
|
-
|
|
45914
|
-
}
|
|
45915
|
-
if (this.stdin) {
|
|
45916
|
-
const chunks = [];
|
|
45917
|
-
for await (const chunk of this.context.stdin) {
|
|
45918
|
-
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
46225
|
+
return 1;
|
|
45919
46226
|
}
|
|
45920
|
-
|
|
45921
|
-
|
|
45922
|
-
|
|
45923
|
-
|
|
45924
|
-
|
|
45925
|
-
|
|
45926
|
-
|
|
45927
|
-
|
|
45928
|
-
|
|
45929
|
-
|
|
46227
|
+
if (this.stdin) {
|
|
46228
|
+
const chunks = [];
|
|
46229
|
+
for await (const chunk of this.context.stdin) {
|
|
46230
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
46231
|
+
}
|
|
46232
|
+
value = Buffer.concat(chunks).toString("utf8").replace(/\n$/, "");
|
|
46233
|
+
} else if (value === void 0) {
|
|
46234
|
+
value = await promptHidden("value: ");
|
|
46235
|
+
}
|
|
46236
|
+
const projectId2 = await resolveProjectId(client, parsed.project);
|
|
46237
|
+
await client.request(`/v1/projects/${projectId2}/secrets`, {
|
|
46238
|
+
method: "POST",
|
|
46239
|
+
body: { env: parsed.environment, key: parsed.key, value }
|
|
46240
|
+
});
|
|
46241
|
+
this.context.stdout.write(`created ${parsed.literal}
|
|
45930
46242
|
`);
|
|
45931
|
-
|
|
46243
|
+
return 0;
|
|
46244
|
+
} catch (err) {
|
|
46245
|
+
return handleExecError(this.context.stderr, err);
|
|
46246
|
+
}
|
|
45932
46247
|
}
|
|
45933
46248
|
};
|
|
45934
46249
|
var SecretGetCommand = class extends Command {
|
|
@@ -45939,39 +46254,44 @@ var SecretGetCommand = class extends Command {
|
|
|
45939
46254
|
alias = options_exports.String({ required: false });
|
|
45940
46255
|
json = options_exports.Boolean("--json", false);
|
|
45941
46256
|
async execute() {
|
|
45942
|
-
|
|
45943
|
-
|
|
45944
|
-
|
|
45945
|
-
|
|
45946
|
-
if (!
|
|
45947
|
-
|
|
45948
|
-
|
|
45949
|
-
|
|
45950
|
-
|
|
45951
|
-
|
|
46257
|
+
try {
|
|
46258
|
+
const client = new ApiClient();
|
|
46259
|
+
await client.ensureHydrated();
|
|
46260
|
+
let alias2 = this.alias;
|
|
46261
|
+
if (!alias2) {
|
|
46262
|
+
if (!isInteractive()) return missingAlias(this.context.stderr);
|
|
46263
|
+
try {
|
|
46264
|
+
alias2 = await pickAliasInteractive(client) ?? void 0;
|
|
46265
|
+
} catch (err) {
|
|
46266
|
+
if (err instanceof UserCancelled) return 130;
|
|
46267
|
+
throw err;
|
|
46268
|
+
}
|
|
46269
|
+
if (!alias2) return 1;
|
|
45952
46270
|
}
|
|
45953
|
-
|
|
45954
|
-
|
|
45955
|
-
|
|
45956
|
-
|
|
45957
|
-
this.context.stderr.write(`keynv: invalid alias '${alias2}'.
|
|
46271
|
+
const parsed = parseAlias(alias2);
|
|
46272
|
+
if (!parsed) {
|
|
46273
|
+
this.context.stderr.write(`keynv: invalid alias '${alias2}'.
|
|
46274
|
+
${ALIAS_FORMAT_HINT}
|
|
45958
46275
|
`);
|
|
45959
|
-
|
|
45960
|
-
|
|
45961
|
-
|
|
45962
|
-
|
|
45963
|
-
|
|
45964
|
-
);
|
|
45965
|
-
if (this.json) {
|
|
45966
|
-
this.context.stdout.write(
|
|
45967
|
-
`${JSON.stringify({ alias: data.alias, version: data.version, value: data.value })}
|
|
45968
|
-
`
|
|
46276
|
+
return 1;
|
|
46277
|
+
}
|
|
46278
|
+
const projectId2 = await resolveProjectId(client, parsed.project);
|
|
46279
|
+
const data = await client.request(
|
|
46280
|
+
`/v1/projects/${projectId2}/secrets/${parsed.environment}/${parsed.key}`
|
|
45969
46281
|
);
|
|
45970
|
-
|
|
45971
|
-
|
|
46282
|
+
if (this.json) {
|
|
46283
|
+
this.context.stdout.write(
|
|
46284
|
+
`${JSON.stringify({ alias: data.alias, version: data.version, value: data.value })}
|
|
46285
|
+
`
|
|
46286
|
+
);
|
|
46287
|
+
} else {
|
|
46288
|
+
this.context.stdout.write(`${data.value}
|
|
45972
46289
|
`);
|
|
46290
|
+
}
|
|
46291
|
+
return 0;
|
|
46292
|
+
} catch (err) {
|
|
46293
|
+
return handleExecError(this.context.stderr, err);
|
|
45973
46294
|
}
|
|
45974
|
-
return 0;
|
|
45975
46295
|
}
|
|
45976
46296
|
};
|
|
45977
46297
|
var SecretListCommand = class extends Command {
|
|
@@ -45982,42 +46302,47 @@ var SecretListCommand = class extends Command {
|
|
|
45982
46302
|
project = options_exports.String({ required: false });
|
|
45983
46303
|
json = options_exports.Boolean("--json", false);
|
|
45984
46304
|
async execute() {
|
|
45985
|
-
|
|
45986
|
-
|
|
45987
|
-
|
|
45988
|
-
|
|
45989
|
-
if (!
|
|
45990
|
-
|
|
45991
|
-
|
|
45992
|
-
|
|
45993
|
-
|
|
45994
|
-
|
|
45995
|
-
|
|
45996
|
-
|
|
45997
|
-
|
|
45998
|
-
|
|
45999
|
-
|
|
46305
|
+
try {
|
|
46306
|
+
const client = new ApiClient();
|
|
46307
|
+
await client.ensureHydrated();
|
|
46308
|
+
let projectName = this.project;
|
|
46309
|
+
if (!projectName) {
|
|
46310
|
+
if (!isInteractive()) {
|
|
46311
|
+
this.context.stderr.write("keynv: missing <project>.\n");
|
|
46312
|
+
return 1;
|
|
46313
|
+
}
|
|
46314
|
+
try {
|
|
46315
|
+
const picked = await pickProject(client, "Project");
|
|
46316
|
+
if (!picked) return 1;
|
|
46317
|
+
projectName = picked.name;
|
|
46318
|
+
} catch (err) {
|
|
46319
|
+
if (err instanceof UserCancelled) return 130;
|
|
46320
|
+
throw err;
|
|
46321
|
+
}
|
|
46000
46322
|
}
|
|
46001
|
-
|
|
46002
|
-
|
|
46003
|
-
|
|
46004
|
-
|
|
46005
|
-
|
|
46323
|
+
const resolvedProjectName = projectName.startsWith("@") ? projectName.slice(1).split(".")[0] ?? projectName : projectName;
|
|
46324
|
+
const projectId2 = await resolveProjectId(client, resolvedProjectName);
|
|
46325
|
+
const data = await client.request(`/v1/projects/${projectId2}/secrets`);
|
|
46326
|
+
if (this.json) {
|
|
46327
|
+
this.context.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
46006
46328
|
`);
|
|
46329
|
+
return 0;
|
|
46330
|
+
}
|
|
46331
|
+
if (data.secrets.length === 0) {
|
|
46332
|
+
this.context.stdout.write("no secrets\n");
|
|
46333
|
+
return 0;
|
|
46334
|
+
}
|
|
46335
|
+
this.context.stdout.write(
|
|
46336
|
+
`${table(
|
|
46337
|
+
["alias", "version", "created_at"],
|
|
46338
|
+
data.secrets.map((s) => [s.alias, String(s.version), s.created_at])
|
|
46339
|
+
)}
|
|
46340
|
+
`
|
|
46341
|
+
);
|
|
46007
46342
|
return 0;
|
|
46343
|
+
} catch (err) {
|
|
46344
|
+
return handleExecError(this.context.stderr, err);
|
|
46008
46345
|
}
|
|
46009
|
-
if (data.secrets.length === 0) {
|
|
46010
|
-
this.context.stdout.write("no secrets\n");
|
|
46011
|
-
return 0;
|
|
46012
|
-
}
|
|
46013
|
-
this.context.stdout.write(
|
|
46014
|
-
`${table(
|
|
46015
|
-
["alias", "version", "created_at"],
|
|
46016
|
-
data.secrets.map((s) => [s.alias, String(s.version), s.created_at])
|
|
46017
|
-
)}
|
|
46018
|
-
`
|
|
46019
|
-
);
|
|
46020
|
-
return 0;
|
|
46021
46346
|
}
|
|
46022
46347
|
};
|
|
46023
46348
|
var SecretRotateCommand = class extends Command {
|
|
@@ -46027,45 +46352,50 @@ var SecretRotateCommand = class extends Command {
|
|
|
46027
46352
|
value = options_exports.String("--value");
|
|
46028
46353
|
stdin = options_exports.Boolean("--stdin", false);
|
|
46029
46354
|
async execute() {
|
|
46030
|
-
|
|
46031
|
-
|
|
46032
|
-
|
|
46033
|
-
|
|
46034
|
-
if (!
|
|
46035
|
-
|
|
46036
|
-
|
|
46037
|
-
|
|
46038
|
-
|
|
46039
|
-
|
|
46355
|
+
try {
|
|
46356
|
+
const client = new ApiClient();
|
|
46357
|
+
await client.ensureHydrated();
|
|
46358
|
+
let alias2 = this.alias;
|
|
46359
|
+
if (!alias2) {
|
|
46360
|
+
if (!isInteractive()) return missingAlias(this.context.stderr);
|
|
46361
|
+
try {
|
|
46362
|
+
alias2 = await pickAliasInteractive(client) ?? void 0;
|
|
46363
|
+
} catch (err) {
|
|
46364
|
+
if (err instanceof UserCancelled) return 130;
|
|
46365
|
+
throw err;
|
|
46366
|
+
}
|
|
46367
|
+
if (!alias2) return 1;
|
|
46040
46368
|
}
|
|
46041
|
-
|
|
46042
|
-
|
|
46043
|
-
|
|
46044
|
-
|
|
46045
|
-
this.context.stderr.write(`keynv: invalid alias '${alias2}'.
|
|
46369
|
+
const parsed = parseAlias(alias2);
|
|
46370
|
+
if (!parsed) {
|
|
46371
|
+
this.context.stderr.write(`keynv: invalid alias '${alias2}'.
|
|
46372
|
+
${ALIAS_FORMAT_HINT}
|
|
46046
46373
|
`);
|
|
46047
|
-
|
|
46048
|
-
}
|
|
46049
|
-
let value;
|
|
46050
|
-
if (this.stdin) {
|
|
46051
|
-
const chunks = [];
|
|
46052
|
-
for await (const chunk of this.context.stdin) {
|
|
46053
|
-
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
46374
|
+
return 1;
|
|
46054
46375
|
}
|
|
46055
|
-
value
|
|
46056
|
-
|
|
46057
|
-
|
|
46058
|
-
|
|
46059
|
-
|
|
46060
|
-
|
|
46061
|
-
|
|
46062
|
-
|
|
46063
|
-
|
|
46064
|
-
|
|
46065
|
-
|
|
46066
|
-
|
|
46376
|
+
let value;
|
|
46377
|
+
if (this.stdin) {
|
|
46378
|
+
const chunks = [];
|
|
46379
|
+
for await (const chunk of this.context.stdin) {
|
|
46380
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
46381
|
+
}
|
|
46382
|
+
value = Buffer.concat(chunks).toString("utf8").replace(/\n$/, "");
|
|
46383
|
+
} else if (this.value !== void 0) {
|
|
46384
|
+
value = this.value;
|
|
46385
|
+
} else {
|
|
46386
|
+
value = await promptHidden("new value: ");
|
|
46387
|
+
}
|
|
46388
|
+
const projectId2 = await resolveProjectId(client, parsed.project);
|
|
46389
|
+
const data = await client.request(
|
|
46390
|
+
`/v1/projects/${projectId2}/secrets/${parsed.environment}/${parsed.key}/rotate`,
|
|
46391
|
+
{ method: "POST", body: { new_value: value } }
|
|
46392
|
+
);
|
|
46393
|
+
this.context.stdout.write(`rotated ${data.alias} \u2192 v${data.version}
|
|
46067
46394
|
`);
|
|
46068
|
-
|
|
46395
|
+
return 0;
|
|
46396
|
+
} catch (err) {
|
|
46397
|
+
return handleExecError(this.context.stderr, err);
|
|
46398
|
+
}
|
|
46069
46399
|
}
|
|
46070
46400
|
};
|
|
46071
46401
|
var SecretDeleteCommand = class extends Command {
|
|
@@ -46073,31 +46403,170 @@ var SecretDeleteCommand = class extends Command {
|
|
|
46073
46403
|
static usage = Command.Usage({ description: "Soft-delete a secret." });
|
|
46074
46404
|
alias = options_exports.String({ required: false });
|
|
46075
46405
|
async execute() {
|
|
46076
|
-
|
|
46077
|
-
|
|
46078
|
-
|
|
46079
|
-
|
|
46080
|
-
if (!
|
|
46081
|
-
|
|
46082
|
-
|
|
46083
|
-
|
|
46084
|
-
|
|
46085
|
-
|
|
46406
|
+
try {
|
|
46407
|
+
const client = new ApiClient();
|
|
46408
|
+
await client.ensureHydrated();
|
|
46409
|
+
let alias2 = this.alias;
|
|
46410
|
+
if (!alias2) {
|
|
46411
|
+
if (!isInteractive()) return missingAlias(this.context.stderr);
|
|
46412
|
+
try {
|
|
46413
|
+
alias2 = await pickAliasInteractive(client) ?? void 0;
|
|
46414
|
+
} catch (err) {
|
|
46415
|
+
if (err instanceof UserCancelled) return 130;
|
|
46416
|
+
throw err;
|
|
46417
|
+
}
|
|
46418
|
+
if (!alias2) return 1;
|
|
46086
46419
|
}
|
|
46087
|
-
|
|
46088
|
-
|
|
46089
|
-
|
|
46090
|
-
|
|
46091
|
-
this.context.stderr.write(`keynv: invalid alias '${alias2}'.
|
|
46420
|
+
const parsed = parseAlias(alias2);
|
|
46421
|
+
if (!parsed) {
|
|
46422
|
+
this.context.stderr.write(`keynv: invalid alias '${alias2}'.
|
|
46423
|
+
${ALIAS_FORMAT_HINT}
|
|
46092
46424
|
`);
|
|
46093
|
-
|
|
46425
|
+
return 1;
|
|
46426
|
+
}
|
|
46427
|
+
const projectId2 = await resolveProjectId(client, parsed.project);
|
|
46428
|
+
await client.request(`/v1/projects/${projectId2}/secrets/${parsed.environment}/${parsed.key}`, {
|
|
46429
|
+
method: "DELETE"
|
|
46430
|
+
});
|
|
46431
|
+
this.context.stdout.write(`deleted ${parsed.literal}
|
|
46432
|
+
`);
|
|
46433
|
+
return 0;
|
|
46434
|
+
} catch (err) {
|
|
46435
|
+
return handleExecError(this.context.stderr, err);
|
|
46094
46436
|
}
|
|
46095
|
-
|
|
46096
|
-
|
|
46097
|
-
|
|
46098
|
-
|
|
46099
|
-
|
|
46437
|
+
}
|
|
46438
|
+
};
|
|
46439
|
+
function generateSecret(bytes = 32) {
|
|
46440
|
+
return randomBytes(bytes).toString("base64");
|
|
46441
|
+
}
|
|
46442
|
+
var ServerInitCommand = class extends Command {
|
|
46443
|
+
static paths = [["server", "init"]];
|
|
46444
|
+
static usage = Command.Usage({
|
|
46445
|
+
description: "Generate secrets and deployment files for a new keynv server.",
|
|
46446
|
+
details: `
|
|
46447
|
+
Interactive onboarding wizard that generates the configuration
|
|
46448
|
+
a fresh keynv server needs: JWT signing secret, master encryption
|
|
46449
|
+
key, and deployment templates.
|
|
46450
|
+
|
|
46451
|
+
Outputs:
|
|
46452
|
+
\u2022 A .keynv-server.env file you feed into your deployment
|
|
46453
|
+
(Docker Compose, Coolify, or bare-metal).
|
|
46454
|
+
\u2022 Optionally a docker-compose.yml in the current directory.
|
|
46455
|
+
|
|
46456
|
+
Non-interactive with --yes to skip prompts.
|
|
46457
|
+
`,
|
|
46458
|
+
examples: [
|
|
46459
|
+
["Interactive wizard", "$0 server init"],
|
|
46460
|
+
["Non-interactive (CI/scripts)", "$0 server init --yes"],
|
|
46461
|
+
["Docker Compose output", "$0 server init --compose"],
|
|
46462
|
+
["Custom output path", "$0 server init --out ./config/env"]
|
|
46463
|
+
]
|
|
46464
|
+
});
|
|
46465
|
+
yes = options_exports.Boolean("--yes", false, {
|
|
46466
|
+
description: "Skip all prompts (non-interactive)."
|
|
46467
|
+
});
|
|
46468
|
+
compose = options_exports.Boolean("--compose", false, {
|
|
46469
|
+
description: "Also write a docker-compose.yml to the current directory."
|
|
46470
|
+
});
|
|
46471
|
+
out = options_exports.String("--out", {
|
|
46472
|
+
description: "Write server env file to this path (default: .keynv-server.env)."
|
|
46473
|
+
});
|
|
46474
|
+
async execute() {
|
|
46475
|
+
const jwtSecret = generateSecret(48);
|
|
46476
|
+
const masterKey = generateSecret(32);
|
|
46477
|
+
const webSessionSecret = generateSecret(48);
|
|
46478
|
+
const outPath = this.out ?? ".keynv-server.env";
|
|
46479
|
+
const contents = [
|
|
46480
|
+
"# Generated by `keynv server init`.",
|
|
46481
|
+
"# Keep this file secret \u2014 it contains cryptographic key material.",
|
|
46482
|
+
`KEYNV_JWT_SECRET=${jwtSecret}`,
|
|
46483
|
+
`KEYNV_MASTER_KEY=${masterKey}`,
|
|
46484
|
+
"",
|
|
46485
|
+
"# Web dashboard session secret (required for Next.js JWE session tokens).",
|
|
46486
|
+
`KEYNV_WEB_SESSION_SECRET=${webSessionSecret}`,
|
|
46487
|
+
"",
|
|
46488
|
+
"# Server configuration (uncomment and adjust):",
|
|
46489
|
+
"# KEYNV_PORT=8080",
|
|
46490
|
+
"# KEYNV_WEB_URL=https://keynv.example.com",
|
|
46491
|
+
"# KEYNV_PUBLIC_REGISTRATION=false",
|
|
46492
|
+
"# KEYNV_DB_PATH=./keynv.db",
|
|
46493
|
+
"# KEYNV_MASTER_KEY_FILE=./master.key",
|
|
46494
|
+
"# KEYNV_RATE_LIMIT_PER_MINUTE=120",
|
|
46495
|
+
"# KEYNV_ARGON2_MEMORY_KIB=46080",
|
|
46496
|
+
"# KEYNV_ARGON2_TIME_COST=3"
|
|
46497
|
+
].join("\n");
|
|
46498
|
+
const { writeFileSync: writeFileSync4 } = await import('fs');
|
|
46499
|
+
writeFileSync4(outPath, `${contents}
|
|
46100
46500
|
`);
|
|
46501
|
+
this.context.stdout.write(`Wrote ${outPath}
|
|
46502
|
+
|
|
46503
|
+
`);
|
|
46504
|
+
this.context.stdout.write("Generated secrets:\n");
|
|
46505
|
+
this.context.stdout.write(
|
|
46506
|
+
` KEYNV_JWT_SECRET ${jwtSecret.slice(0, 8)}... (base64, 48 bytes)
|
|
46507
|
+
`
|
|
46508
|
+
);
|
|
46509
|
+
this.context.stdout.write(
|
|
46510
|
+
` KEYNV_MASTER_KEY ${masterKey.slice(0, 8)}... (base64, 32 bytes)
|
|
46511
|
+
`
|
|
46512
|
+
);
|
|
46513
|
+
this.context.stdout.write(
|
|
46514
|
+
` KEYNV_WEB_SESSION_SECRET ${webSessionSecret.slice(0, 8)}... (base64, 48 bytes)
|
|
46515
|
+
`
|
|
46516
|
+
);
|
|
46517
|
+
this.context.stdout.write("\nNext steps:\n");
|
|
46518
|
+
this.context.stdout.write(` 1. Feed ${outPath} into your deployment:
|
|
46519
|
+
`);
|
|
46520
|
+
this.context.stdout.write(" docker compose --env-file .keynv-server.env up -d\n");
|
|
46521
|
+
this.context.stdout.write(" or load it into Coolify as environment variables.\n");
|
|
46522
|
+
this.context.stdout.write(" 2. Bootstrap the first owner:\n");
|
|
46523
|
+
this.context.stdout.write(
|
|
46524
|
+
" docker exec -it <server-container> node dist/bootstrap.js \\\n"
|
|
46525
|
+
);
|
|
46526
|
+
this.context.stdout.write(" --owner-email alice@example.com \\\n");
|
|
46527
|
+
this.context.stdout.write(' --owner-password "<a long random password>" \\\n');
|
|
46528
|
+
this.context.stdout.write(' --org-name "Acme Inc"\n');
|
|
46529
|
+
this.context.stdout.write(" 3. Install the CLI and log in:\n");
|
|
46530
|
+
this.context.stdout.write(" npm install -g @keynv/cli\n");
|
|
46531
|
+
this.context.stdout.write(" keynv login --server https://keynv.example.com\n");
|
|
46532
|
+
this.context.stdout.write(" 4. Onboard your first project:\n");
|
|
46533
|
+
this.context.stdout.write(" cd your-project && keynv init\n");
|
|
46534
|
+
if (this.compose) {
|
|
46535
|
+
const composeContent = `# keynv server + Litestream sidecar
|
|
46536
|
+
# Generated by \`keynv server init\`.
|
|
46537
|
+
# Load with: docker compose --env-file .keynv-server.env up -d
|
|
46538
|
+
|
|
46539
|
+
services:
|
|
46540
|
+
server:
|
|
46541
|
+
image: ghcr.io/keynv-labs/keynv-server:latest
|
|
46542
|
+
restart: unless-stopped
|
|
46543
|
+
ports:
|
|
46544
|
+
- '8080:8080'
|
|
46545
|
+
env_file:
|
|
46546
|
+
- .keynv-server.env
|
|
46547
|
+
volumes:
|
|
46548
|
+
- keynv-data:/data
|
|
46549
|
+
|
|
46550
|
+
litestream:
|
|
46551
|
+
image: litestream/litestream:latest
|
|
46552
|
+
restart: unless-stopped
|
|
46553
|
+
volumes:
|
|
46554
|
+
- keynv-data:/data
|
|
46555
|
+
command: replicate
|
|
46556
|
+
environment:
|
|
46557
|
+
LITESTREAM_DB_PATH: /data/keynv.db
|
|
46558
|
+
LITESTREAM_REPLICA_URL: s3://your-bucket/keynv
|
|
46559
|
+
AWS_ACCESS_KEY_ID: \${LITESTREAM_AWS_ACCESS_KEY_ID}
|
|
46560
|
+
AWS_SECRET_ACCESS_KEY: \${LITESTREAM_AWS_SECRET_ACCESS_KEY}
|
|
46561
|
+
|
|
46562
|
+
volumes:
|
|
46563
|
+
keynv-data:
|
|
46564
|
+
`;
|
|
46565
|
+
writeFileSync4("docker-compose.yml", composeContent);
|
|
46566
|
+
this.context.stdout.write(
|
|
46567
|
+
"\nWrote docker-compose.yml (update Litestream S3 config before deploying).\n"
|
|
46568
|
+
);
|
|
46569
|
+
}
|
|
46101
46570
|
return 0;
|
|
46102
46571
|
}
|
|
46103
46572
|
};
|
|
@@ -46453,18 +46922,9 @@ var TESTERS = [
|
|
|
46453
46922
|
sshTester,
|
|
46454
46923
|
httpTester
|
|
46455
46924
|
];
|
|
46456
|
-
function findTester(type) {
|
|
46457
|
-
return TESTERS.find((t) => t.type === type) ?? null;
|
|
46458
|
-
}
|
|
46459
46925
|
|
|
46460
46926
|
// src/commands/test.ts
|
|
46461
46927
|
init_http();
|
|
46462
|
-
async function findProjectIdByName3(client, name) {
|
|
46463
|
-
const data = await client.request("/v1/projects");
|
|
46464
|
-
const match = data.projects.find((p2) => p2.name === name);
|
|
46465
|
-
if (!match) throw new Error(`unknown project: ${name}`);
|
|
46466
|
-
return match.id;
|
|
46467
|
-
}
|
|
46468
46928
|
function parseTargets(specs) {
|
|
46469
46929
|
const out = {};
|
|
46470
46930
|
for (const spec of specs) {
|
|
@@ -46518,12 +46978,17 @@ the error message.
|
|
|
46518
46978
|
);
|
|
46519
46979
|
return 2;
|
|
46520
46980
|
}
|
|
46521
|
-
const tester =
|
|
46981
|
+
const tester = TESTERS.find((t) => t.type === this.as);
|
|
46522
46982
|
if (!tester) {
|
|
46523
46983
|
this.context.stderr.write(`keynv: unknown tester '${this.as}'
|
|
46524
46984
|
`);
|
|
46525
46985
|
return 1;
|
|
46526
46986
|
}
|
|
46987
|
+
const timeoutMs = this.timeout ? Number.parseInt(this.timeout, 10) * 1e3 : void 0;
|
|
46988
|
+
if (this.timeout && (timeoutMs === void 0 || Number.isNaN(timeoutMs))) {
|
|
46989
|
+
this.context.stderr.write("keynv: invalid --timeout (expected integer seconds)\n");
|
|
46990
|
+
return 2;
|
|
46991
|
+
}
|
|
46527
46992
|
let target;
|
|
46528
46993
|
try {
|
|
46529
46994
|
target = parseTargets(this.targets ?? []);
|
|
@@ -46540,7 +47005,7 @@ the error message.
|
|
|
46540
47005
|
}
|
|
46541
47006
|
let value;
|
|
46542
47007
|
try {
|
|
46543
|
-
const projectId2 = await
|
|
47008
|
+
const projectId2 = await resolveProjectId(client, parsedAlias.project);
|
|
46544
47009
|
const data = await client.request(
|
|
46545
47010
|
`/v1/projects/${projectId2}/secrets/${parsedAlias.environment}/${parsedAlias.key}`
|
|
46546
47011
|
);
|
|
@@ -46551,11 +47016,6 @@ the error message.
|
|
|
46551
47016
|
`);
|
|
46552
47017
|
return 1;
|
|
46553
47018
|
}
|
|
46554
|
-
const timeoutMs = this.timeout ? Number.parseInt(this.timeout, 10) * 1e3 : void 0;
|
|
46555
|
-
if (this.timeout && (timeoutMs === void 0 || Number.isNaN(timeoutMs))) {
|
|
46556
|
-
this.context.stderr.write("keynv: invalid --timeout (expected integer seconds)\n");
|
|
46557
|
-
return 2;
|
|
46558
|
-
}
|
|
46559
47019
|
const result = await runTest({
|
|
46560
47020
|
tester,
|
|
46561
47021
|
secret: { alias: parsedAlias.literal, value },
|
|
@@ -46597,25 +47057,6 @@ the error message.
|
|
|
46597
47057
|
}
|
|
46598
47058
|
};
|
|
46599
47059
|
|
|
46600
|
-
// src/commands/ui.ts
|
|
46601
|
-
init_tty();
|
|
46602
|
-
init_menu();
|
|
46603
|
-
var UICommand = class extends Command {
|
|
46604
|
-
static paths = [["ui"]];
|
|
46605
|
-
static usage = Command.Usage({
|
|
46606
|
-
description: "Open the interactive menu (also runs by default when no args are given)."
|
|
46607
|
-
});
|
|
46608
|
-
async execute() {
|
|
46609
|
-
if (!isInteractive()) {
|
|
46610
|
-
this.context.stdout.write(
|
|
46611
|
-
"keynv \u2014 AI-safe secrets management.\nRun `keynv --help` for the full command list, or run `keynv` in an interactive\nterminal to open the menu.\n"
|
|
46612
|
-
);
|
|
46613
|
-
return 0;
|
|
46614
|
-
}
|
|
46615
|
-
return runMenu();
|
|
46616
|
-
}
|
|
46617
|
-
};
|
|
46618
|
-
|
|
46619
47060
|
// src/index.ts
|
|
46620
47061
|
init_format();
|
|
46621
47062
|
init_version();
|
|
@@ -46649,7 +47090,7 @@ cli.register(InitCommand);
|
|
|
46649
47090
|
cli.register(RedactCommand);
|
|
46650
47091
|
cli.register(RedactStreamCommand);
|
|
46651
47092
|
cli.register(TestCommand);
|
|
46652
|
-
cli.register(
|
|
47093
|
+
cli.register(ServerInitCommand);
|
|
46653
47094
|
var argv = process.argv.slice(2);
|
|
46654
47095
|
if (argv.length === 0) {
|
|
46655
47096
|
const { runMenu: runMenu2 } = await Promise.resolve().then(() => (init_menu(), menu_exports));
|