@keynv/cli 0.1.0-rc.16 → 0.1.0-rc.18
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 +129 -51
- package/dist/index.js.map +1 -1
- package/package.json +3 -15
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
3
|
import { createHash, randomBytes } from 'crypto';
|
|
4
|
-
import { readFileSync,
|
|
4
|
+
import { readFileSync, unlinkSync, existsSync, statSync, writeFileSync, readdirSync, mkdirSync, rmSync, lstatSync } from 'fs';
|
|
5
5
|
import { hostname, platform, homedir } from 'os';
|
|
6
6
|
import * as nodePath from 'path';
|
|
7
7
|
import { relative, join, isAbsolute, resolve, dirname, basename } from 'path';
|
|
@@ -1057,7 +1057,7 @@ var require_lib = __commonJS({
|
|
|
1057
1057
|
var VERSION, AGENT;
|
|
1058
1058
|
var init_version = __esm({
|
|
1059
1059
|
"src/version.ts"() {
|
|
1060
|
-
VERSION = "0.1.0-rc.
|
|
1060
|
+
VERSION = "0.1.0-rc.18" ;
|
|
1061
1061
|
AGENT = `keynv-cli/${VERSION}`;
|
|
1062
1062
|
}
|
|
1063
1063
|
});
|
|
@@ -5734,7 +5734,7 @@ var init_http = __esm({
|
|
|
5734
5734
|
async request(path, opts = {}) {
|
|
5735
5735
|
if (this.hydrated) await this.hydrated;
|
|
5736
5736
|
if (!this.creds && opts.authed !== false) {
|
|
5737
|
-
throw clientError(401, "auth.missing_token", "Not
|
|
5737
|
+
throw clientError(401, "auth.missing_token", "Not connected. Run `keynv` first.");
|
|
5738
5738
|
}
|
|
5739
5739
|
const headers = {
|
|
5740
5740
|
"content-type": "application/json",
|
|
@@ -5778,6 +5778,13 @@ var init_http = __esm({
|
|
|
5778
5778
|
Check the server is running: curl ${serverUrl}/v1/health`
|
|
5779
5779
|
);
|
|
5780
5780
|
}
|
|
5781
|
+
} else {
|
|
5782
|
+
this.creds = null;
|
|
5783
|
+
throw clientError(
|
|
5784
|
+
401,
|
|
5785
|
+
"auth.session_expired",
|
|
5786
|
+
"Session expired. Run `keynv` to reconnect."
|
|
5787
|
+
);
|
|
5781
5788
|
}
|
|
5782
5789
|
}
|
|
5783
5790
|
if (res.status === 204) return void 0;
|
|
@@ -5994,7 +6001,7 @@ When the user asks about secrets or env vars, follow this decision tree:
|
|
|
5994
6001
|
| User intent | Run / suggest |
|
|
5995
6002
|
|---|---|
|
|
5996
6003
|
| "Add a new API key" / "set up a secret" | \`keynv secret create\` (interactive prompt picks project + env + key) |
|
|
5997
|
-
| "I have a \`.env\`, migrate it" | \`keynv
|
|
6004
|
+
| "I have a \`.env\`, migrate it" | Run \`keynv\` and choose **Set up this project** |
|
|
5998
6005
|
| "Run the app / dev server / tests" | \`keynv exec -- <their existing command>\` (auto-loads \`.keynv.env\`) |
|
|
5999
6006
|
| "Show me the value of X" | \`keynv secret get @alias\` \u2014 but copy to clipboard, do NOT print the value into chat / terminal |
|
|
6000
6007
|
| "Rotate this key" | \`keynv secret rotate @alias\` |
|
|
@@ -6021,7 +6028,7 @@ keynv whoami # who am I logged in as
|
|
|
6021
6028
|
keynv --help # full command list
|
|
6022
6029
|
\`\`\`
|
|
6023
6030
|
|
|
6024
|
-
If \`.keynv.env\` is missing or empty, the user probably hasn't run
|
|
6031
|
+
If \`.keynv.env\` is missing or empty, the user probably hasn't run setup yet \u2014 suggest running \`keynv\` and choosing **Set up this project** before they paste a secret into a \`.env\`.`;
|
|
6025
6032
|
}
|
|
6026
6033
|
function renderKeynvBlock() {
|
|
6027
6034
|
return `${KEYNV_BLOCK_START}
|
|
@@ -6060,7 +6067,7 @@ var AGENTS_FILE_BASENAME, KEYNV_BLOCK_START, KEYNV_BLOCK_END;
|
|
|
6060
6067
|
var init_ai_context = __esm({
|
|
6061
6068
|
"src/init/ai-context.ts"() {
|
|
6062
6069
|
AGENTS_FILE_BASENAME = "AGENTS.md";
|
|
6063
|
-
KEYNV_BLOCK_START = "<!-- keynv:start -- generated by
|
|
6070
|
+
KEYNV_BLOCK_START = "<!-- keynv:start -- generated by keynv setup; safe to leave intact, will be refreshed on re-run -->";
|
|
6064
6071
|
KEYNV_BLOCK_END = "<!-- keynv:end -->";
|
|
6065
6072
|
}
|
|
6066
6073
|
});
|
|
@@ -8605,11 +8612,11 @@ function toAliasKey(name) {
|
|
|
8605
8612
|
return normalised.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64) || "key";
|
|
8606
8613
|
}
|
|
8607
8614
|
async function runInitFlow(client, opts) {
|
|
8608
|
-
ge("
|
|
8615
|
+
ge("Set up this project");
|
|
8609
8616
|
const root = findProjectRoot(opts.cwd);
|
|
8610
8617
|
if (root === null) {
|
|
8611
8618
|
me(
|
|
8612
|
-
|
|
8619
|
+
'Couldn\'t find a project root (no package.json, pyproject.toml, Cargo.toml, go.mod, or .git anywhere up the tree). Run `keynv` inside a project directory and choose "Set up this project".'
|
|
8613
8620
|
);
|
|
8614
8621
|
return { exitCode: 1 };
|
|
8615
8622
|
}
|
|
@@ -9063,7 +9070,7 @@ function composeKeynvEnv(opts) {
|
|
|
9063
9070
|
lines.push(...trimmed.split("\n"));
|
|
9064
9071
|
lines.push("");
|
|
9065
9072
|
}
|
|
9066
|
-
lines.push(`# >>> keynv
|
|
9073
|
+
lines.push(`# >>> keynv setup ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)} >>>`);
|
|
9067
9074
|
} else {
|
|
9068
9075
|
lines.push("# .keynv.env \u2014 alias references to vault secrets.");
|
|
9069
9076
|
lines.push("# Safe to commit: this file contains references, not values.");
|
|
@@ -9085,7 +9092,7 @@ function composeKeynvEnv(opts) {
|
|
|
9085
9092
|
}
|
|
9086
9093
|
}
|
|
9087
9094
|
if (mergeWithExisting !== null) {
|
|
9088
|
-
lines.push("# <<< keynv
|
|
9095
|
+
lines.push("# <<< keynv setup <<<");
|
|
9089
9096
|
}
|
|
9090
9097
|
return lines;
|
|
9091
9098
|
}
|
|
@@ -9227,7 +9234,7 @@ async function runBrowserAuth(serverUrl) {
|
|
|
9227
9234
|
access_expires_at: new Date(Date.now() + data.expires_in * 1e3).toISOString()
|
|
9228
9235
|
};
|
|
9229
9236
|
}
|
|
9230
|
-
throw new BrowserAuthError("Browser auth timed out. Run `keynv
|
|
9237
|
+
throw new BrowserAuthError("Browser auth timed out. Run `keynv` to try again.");
|
|
9231
9238
|
}
|
|
9232
9239
|
var BrowserAuthError;
|
|
9233
9240
|
var init_browser_auth = __esm({
|
|
@@ -43651,7 +43658,7 @@ var init_audit2 = __esm({
|
|
|
43651
43658
|
async function runLoginFlow(client, options = {}) {
|
|
43652
43659
|
const server = options.server ?? DEFAULT_SERVER_URL;
|
|
43653
43660
|
const s = ft();
|
|
43654
|
-
s.start(
|
|
43661
|
+
s.start(`Opening browser for ${server}`);
|
|
43655
43662
|
let creds;
|
|
43656
43663
|
try {
|
|
43657
43664
|
creds = await runBrowserAuth(server);
|
|
@@ -43891,17 +43898,21 @@ async function runMenu() {
|
|
|
43891
43898
|
await client.ensureHydrated();
|
|
43892
43899
|
let didLogin = false;
|
|
43893
43900
|
if (!client.isLoggedIn) {
|
|
43894
|
-
|
|
43901
|
+
const server = await pickConnectionTarget();
|
|
43902
|
+
if (server === null) {
|
|
43903
|
+
ye("Bye.");
|
|
43904
|
+
return 0;
|
|
43905
|
+
}
|
|
43895
43906
|
try {
|
|
43896
|
-
const ok = await runLoginFlow(client);
|
|
43907
|
+
const ok = await runLoginFlow(client, { server });
|
|
43897
43908
|
if (!ok) {
|
|
43898
|
-
ye("
|
|
43909
|
+
ye("Connection cancelled.");
|
|
43899
43910
|
return 1;
|
|
43900
43911
|
}
|
|
43901
43912
|
didLogin = true;
|
|
43902
43913
|
} catch (err) {
|
|
43903
43914
|
if (err instanceof UserCancelled) {
|
|
43904
|
-
me("
|
|
43915
|
+
me("Connection cancelled.");
|
|
43905
43916
|
return 130;
|
|
43906
43917
|
}
|
|
43907
43918
|
throw err;
|
|
@@ -43938,7 +43949,7 @@ async function runMenu() {
|
|
|
43938
43949
|
const value = await Ee({
|
|
43939
43950
|
message: "What now?",
|
|
43940
43951
|
options: [
|
|
43941
|
-
{ value: "init", label: "
|
|
43952
|
+
{ value: "init", label: "Set up this project", hint: "migrate .env into keynv" },
|
|
43942
43953
|
{ value: "projects", label: "Projects" },
|
|
43943
43954
|
{ value: "secrets", label: "Secrets" },
|
|
43944
43955
|
{ value: "members", label: "Members" },
|
|
@@ -43994,9 +44005,41 @@ async function runMenu() {
|
|
|
43994
44005
|
}
|
|
43995
44006
|
}
|
|
43996
44007
|
}
|
|
44008
|
+
async function pickConnectionTarget() {
|
|
44009
|
+
R2.info("Connect once, then manage projects and secrets from this menu.");
|
|
44010
|
+
const target = await Ee({
|
|
44011
|
+
message: "Connect to keynv",
|
|
44012
|
+
options: [
|
|
44013
|
+
{ value: "cloud", label: "keynv.dev", hint: "recommended" },
|
|
44014
|
+
{ value: "self-hosted", label: "Self-hosted server", hint: "enter your API URL" },
|
|
44015
|
+
{ value: "exit", label: "Exit" }
|
|
44016
|
+
]
|
|
44017
|
+
});
|
|
44018
|
+
if (q(target) || target === "exit") return null;
|
|
44019
|
+
if (target === "cloud") return DEFAULT_SERVER_URL;
|
|
44020
|
+
const server = await Re({
|
|
44021
|
+
message: "Server API URL",
|
|
44022
|
+
placeholder: "https://api.keynv.example.com",
|
|
44023
|
+
validate: (value) => {
|
|
44024
|
+
if (!value) return "Enter a server URL.";
|
|
44025
|
+
try {
|
|
44026
|
+
const parsed = new URL(value);
|
|
44027
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
44028
|
+
return "Use an http:// or https:// URL.";
|
|
44029
|
+
}
|
|
44030
|
+
return void 0;
|
|
44031
|
+
} catch {
|
|
44032
|
+
return "Enter a valid URL.";
|
|
44033
|
+
}
|
|
44034
|
+
}
|
|
44035
|
+
});
|
|
44036
|
+
if (q(server)) return null;
|
|
44037
|
+
return String(server);
|
|
44038
|
+
}
|
|
43997
44039
|
var init_menu = __esm({
|
|
43998
44040
|
"src/ui/menu.ts"() {
|
|
43999
44041
|
init_dist5();
|
|
44042
|
+
init_defaults();
|
|
44000
44043
|
init_http();
|
|
44001
44044
|
init_store();
|
|
44002
44045
|
init_detect();
|
|
@@ -46364,7 +46407,7 @@ spelled \`--from\` to avoid the collision.)
|
|
|
46364
46407
|
const client = new ApiClient();
|
|
46365
46408
|
await client.ensureHydrated();
|
|
46366
46409
|
if (!client.isLoggedIn) {
|
|
46367
|
-
this.context.stderr.write("keynv: not
|
|
46410
|
+
this.context.stderr.write("keynv: not connected. Run `keynv` first.\n");
|
|
46368
46411
|
return 1;
|
|
46369
46412
|
}
|
|
46370
46413
|
let envFileLoaded = null;
|
|
@@ -46393,7 +46436,7 @@ spelled \`--from\` to avoid the collision.)
|
|
|
46393
46436
|
if (!envFileLoaded && !this.noEnvFile && !this.envFile && !process.env.KEYNV_ENV_FILE) {
|
|
46394
46437
|
this.context.stderr.write(
|
|
46395
46438
|
`keynv: no ${ENV_FILE_BASENAME} found in this directory or any parent.
|
|
46396
|
-
Run \`keynv
|
|
46439
|
+
Run \`keynv\` in your project root and choose "Set up this project" to create one.
|
|
46397
46440
|
`
|
|
46398
46441
|
);
|
|
46399
46442
|
}
|
|
@@ -46682,6 +46725,28 @@ function toAliasKey2(name) {
|
|
|
46682
46725
|
if (KEY_RE3.test(normalised)) return normalised;
|
|
46683
46726
|
return normalised.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 64) || "key";
|
|
46684
46727
|
}
|
|
46728
|
+
function escapeRegExp2(value) {
|
|
46729
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
46730
|
+
}
|
|
46731
|
+
function writeKeynvEnvMappings(path, projectName, envName, secrets) {
|
|
46732
|
+
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
46733
|
+
const lines = [];
|
|
46734
|
+
for (const s of secrets) {
|
|
46735
|
+
const hasEntry = new RegExp(`^${escapeRegExp2(s.name)}=`, "m").test(existing);
|
|
46736
|
+
if (!hasEntry) {
|
|
46737
|
+
lines.push(`# ${s.name}`, `${s.name}=@${projectName}.${envName}.${s.aliasKey}`);
|
|
46738
|
+
}
|
|
46739
|
+
}
|
|
46740
|
+
if (lines.length === 0) return 0;
|
|
46741
|
+
const prefix = existing.trimEnd();
|
|
46742
|
+
const block = `# Generated by keynv setup
|
|
46743
|
+
${lines.join("\n")}
|
|
46744
|
+
`;
|
|
46745
|
+
writeFileSync(path, prefix ? `${prefix}
|
|
46746
|
+
|
|
46747
|
+
${block}` : block);
|
|
46748
|
+
return lines.length / 2;
|
|
46749
|
+
}
|
|
46685
46750
|
var InitCommand = class extends Command {
|
|
46686
46751
|
static paths = [["init"]];
|
|
46687
46752
|
static usage = Command.Usage({
|
|
@@ -46737,7 +46802,7 @@ any prompts.
|
|
|
46737
46802
|
const client = new ApiClient();
|
|
46738
46803
|
await client.ensureHydrated();
|
|
46739
46804
|
if (!client.isLoggedIn) {
|
|
46740
|
-
this.context.stderr.write("keynv: not
|
|
46805
|
+
this.context.stderr.write("keynv: not connected. Run `keynv` first.\n");
|
|
46741
46806
|
return 1;
|
|
46742
46807
|
}
|
|
46743
46808
|
const hasEnvFile = this.envFile != null;
|
|
@@ -46838,7 +46903,22 @@ any prompts.
|
|
|
46838
46903
|
return 0;
|
|
46839
46904
|
}
|
|
46840
46905
|
const secretsWithKeys = secrets.map((s) => ({ ...s, aliasKey: toAliasKey2(s.name) }));
|
|
46841
|
-
|
|
46906
|
+
const result = await this.uploadSecrets(
|
|
46907
|
+
client,
|
|
46908
|
+
projectId2,
|
|
46909
|
+
projectName,
|
|
46910
|
+
envName,
|
|
46911
|
+
secretsWithKeys
|
|
46912
|
+
);
|
|
46913
|
+
if (result !== 0) return result;
|
|
46914
|
+
const keynvEnvPath = join(process.cwd(), ".keynv.env");
|
|
46915
|
+
const written = writeKeynvEnvMappings(keynvEnvPath, projectName, envName, secretsWithKeys);
|
|
46916
|
+
this.context.stdout.write(
|
|
46917
|
+
written > 0 ? `keynv: wrote ${keynvEnvPath}
|
|
46918
|
+
` : `keynv: ${keynvEnvPath} already up to date
|
|
46919
|
+
`
|
|
46920
|
+
);
|
|
46921
|
+
return 0;
|
|
46842
46922
|
}
|
|
46843
46923
|
/**
|
|
46844
46924
|
* Auto-scan mode (--yes without explicit --env-file). Finds .env files
|
|
@@ -46912,14 +46992,13 @@ any prompts.
|
|
|
46912
46992
|
secretsWithKeys
|
|
46913
46993
|
);
|
|
46914
46994
|
if (result !== 0) return result;
|
|
46915
|
-
const aliasLines = secretsWithKeys.map((s) => `# ${s.name}
|
|
46916
|
-
${s.name}=@${projectName}.${envName}.${s.aliasKey}`).join("\n");
|
|
46917
46995
|
const keynvEnvPath = join(root.path, ".keynv.env");
|
|
46918
|
-
|
|
46919
|
-
|
|
46920
|
-
`
|
|
46921
|
-
|
|
46922
|
-
`
|
|
46996
|
+
const written = writeKeynvEnvMappings(keynvEnvPath, projectName, envName, secretsWithKeys);
|
|
46997
|
+
this.context.stdout.write(
|
|
46998
|
+
written > 0 ? `keynv: wrote ${keynvEnvPath}
|
|
46999
|
+
` : `keynv: ${keynvEnvPath} already up to date
|
|
47000
|
+
`
|
|
47001
|
+
);
|
|
46923
47002
|
try {
|
|
46924
47003
|
const outcome = writeAiContext(root.path);
|
|
46925
47004
|
if (outcome === "created") this.context.stdout.write("keynv: wrote AGENTS.md\n");
|
|
@@ -47242,7 +47321,7 @@ var WhoamiCommand = class extends Command {
|
|
|
47242
47321
|
const client = new ApiClient();
|
|
47243
47322
|
await client.ensureHydrated();
|
|
47244
47323
|
if (!client.isLoggedIn) {
|
|
47245
|
-
this.context.stderr.write("keynv: not
|
|
47324
|
+
this.context.stderr.write("keynv: not connected. Run `keynv`.\n");
|
|
47246
47325
|
return 1;
|
|
47247
47326
|
}
|
|
47248
47327
|
const data = await client.request("/v1/whoami");
|
|
@@ -47819,11 +47898,11 @@ Non-interactive with --yes to skip prompts.
|
|
|
47819
47898
|
this.context.stdout.write(" --owner-email alice@example.com \\\n");
|
|
47820
47899
|
this.context.stdout.write(' --owner-password "<a long random password>" \\\n');
|
|
47821
47900
|
this.context.stdout.write(' --org-name "Acme Inc"\n');
|
|
47822
|
-
this.context.stdout.write(" 3. Install the CLI and
|
|
47901
|
+
this.context.stdout.write(" 3. Install the CLI and connect from the TUI:\n");
|
|
47823
47902
|
this.context.stdout.write(" npm install -g @keynv/cli\n");
|
|
47824
|
-
this.context.stdout.write(" keynv
|
|
47825
|
-
this.context.stdout.write(" 4. Onboard your first project:\n");
|
|
47826
|
-
this.context.stdout.write(" cd your-project && keynv
|
|
47903
|
+
this.context.stdout.write(" keynv\n");
|
|
47904
|
+
this.context.stdout.write(" 4. Onboard your first project from the same TUI:\n");
|
|
47905
|
+
this.context.stdout.write(" cd your-project && keynv\n");
|
|
47827
47906
|
if (this.compose) {
|
|
47828
47907
|
const composeContent = `# keynv server + Litestream sidecar
|
|
47829
47908
|
# Generated by \`keynv server init\`.
|
|
@@ -48409,7 +48488,7 @@ the error message.
|
|
|
48409
48488
|
const client = new ApiClient();
|
|
48410
48489
|
await client.ensureHydrated();
|
|
48411
48490
|
if (!client.isLoggedIn) {
|
|
48412
|
-
this.context.stderr.write("keynv: not
|
|
48491
|
+
this.context.stderr.write("keynv: not connected. Run `keynv` first.\n");
|
|
48413
48492
|
return 1;
|
|
48414
48493
|
}
|
|
48415
48494
|
let value;
|
|
@@ -48501,28 +48580,27 @@ cli.register(RedactStreamCommand);
|
|
|
48501
48580
|
cli.register(TestCommand);
|
|
48502
48581
|
cli.register(ServerInitCommand);
|
|
48503
48582
|
var argv = process.argv.slice(2);
|
|
48504
|
-
|
|
48505
|
-
|
|
48506
|
-
|
|
48507
|
-
|
|
48508
|
-
|
|
48509
|
-
|
|
48510
|
-
|
|
48511
|
-
process.exit(1);
|
|
48512
|
-
});
|
|
48513
|
-
} else {
|
|
48583
|
+
async function main() {
|
|
48584
|
+
if (argv.length === 0) {
|
|
48585
|
+
const { runMenu: runMenu2 } = await Promise.resolve().then(() => (init_menu(), menu_exports));
|
|
48586
|
+
const { isInteractive: isInteractive2 } = await Promise.resolve().then(() => (init_tty(), tty_exports));
|
|
48587
|
+
if (isInteractive2()) {
|
|
48588
|
+
return runMenu2();
|
|
48589
|
+
}
|
|
48514
48590
|
process.stdout.write(
|
|
48515
48591
|
"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"
|
|
48516
48592
|
);
|
|
48517
|
-
|
|
48593
|
+
return 0;
|
|
48518
48594
|
}
|
|
48519
|
-
|
|
48520
|
-
cli.run(argv).then((code) => process.exit(code ?? 0)).catch((err) => {
|
|
48521
|
-
process.stderr.write(`${fmtError(err)}
|
|
48522
|
-
`);
|
|
48523
|
-
process.exit(1);
|
|
48524
|
-
});
|
|
48595
|
+
return cli.run(argv);
|
|
48525
48596
|
}
|
|
48597
|
+
main().then((code) => {
|
|
48598
|
+
process.exitCode = code ?? 0;
|
|
48599
|
+
}).catch((err) => {
|
|
48600
|
+
process.stderr.write(`${fmtError(err)}
|
|
48601
|
+
`);
|
|
48602
|
+
process.exitCode = 1;
|
|
48603
|
+
});
|
|
48526
48604
|
/*! Bundled license information:
|
|
48527
48605
|
|
|
48528
48606
|
long/umd/index.js:
|