@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 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, writeFileSync, unlinkSync, existsSync, statSync, readdirSync, mkdirSync, rmSync, lstatSync } from 'fs';
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.16" ;
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 logged in. Run `keynv login` first.");
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 init\` (you're seeing this file because that already happened) |
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 \`keynv init\` yet \u2014 suggest they do so before they paste a secret into a \`.env\`.`;
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 `keynv init`; safe to leave intact, will be refreshed on re-run -->";
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("keynv init");
8615
+ ge("Set up this project");
8609
8616
  const root = findProjectRoot(opts.cwd);
8610
8617
  if (root === null) {
8611
8618
  me(
8612
- "Couldn't find a project root (no package.json, pyproject.toml, Cargo.toml, go.mod, or .git anywhere up the tree). Run `keynv init` inside a project directory."
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 init ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)} >>>`);
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 init <<<");
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 login` to try again.");
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("Waiting for authorization");
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
- R2.info("Not logged in.");
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("Login cancelled.");
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("Login cancelled.");
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: "Initialize this project (migrate .env)", hint: "keynv init" },
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 logged in. Run `keynv login` first.\n");
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 init\` in your project root to migrate secrets and create one.
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 logged in. Run `keynv login` first.\n");
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
- return this.uploadSecrets(client, projectId2, projectName, envName, secretsWithKeys);
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
- writeFileSync(keynvEnvPath, `# Auto-generated by keynv init --yes
46919
- ${aliasLines}
46920
- `);
46921
- this.context.stdout.write(`keynv: wrote ${keynvEnvPath}
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 logged in. Run `keynv login`.\n");
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 log in:\n");
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 login --server https://keynv.example.com\n");
47825
- this.context.stdout.write(" 4. Onboard your first project:\n");
47826
- this.context.stdout.write(" cd your-project && keynv init\n");
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 logged in. Run `keynv login` first.\n");
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
- if (argv.length === 0) {
48505
- const { runMenu: runMenu2 } = await Promise.resolve().then(() => (init_menu(), menu_exports));
48506
- const { isInteractive: isInteractive2 } = await Promise.resolve().then(() => (init_tty(), tty_exports));
48507
- if (isInteractive2()) {
48508
- runMenu2().then((code) => process.exit(code)).catch((err) => {
48509
- process.stderr.write(`${fmtError(err)}
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
- process.exit(0);
48593
+ return 0;
48518
48594
  }
48519
- } else {
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: