@keynv/cli 0.1.0-rc.10 → 0.1.0-rc.11
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 +386 -254
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
3
|
import 'crypto';
|
|
4
4
|
import { readFileSync, existsSync, statSync, writeFileSync, unlinkSync, mkdirSync, rmSync, readdirSync } from 'fs';
|
|
5
|
-
import { homedir } from 'os';
|
|
5
|
+
import { hostname, platform, homedir } from 'os';
|
|
6
6
|
import { isAbsolute, resolve, join, dirname, basename } from 'path';
|
|
7
7
|
import { Entry } from '@napi-rs/keyring';
|
|
8
8
|
import { styleText } from 'util';
|
|
@@ -579,16 +579,16 @@ var require_lib = __commonJS({
|
|
|
579
579
|
}
|
|
580
580
|
var TypeAssertionError = class extends Error {
|
|
581
581
|
constructor({ errors } = {}) {
|
|
582
|
-
let
|
|
582
|
+
let errorMessage2 = `Type mismatch`;
|
|
583
583
|
if (errors && errors.length > 0) {
|
|
584
|
-
|
|
584
|
+
errorMessage2 += `
|
|
585
585
|
`;
|
|
586
586
|
for (const error of errors) {
|
|
587
|
-
|
|
587
|
+
errorMessage2 += `
|
|
588
588
|
- ${error}`;
|
|
589
589
|
}
|
|
590
590
|
}
|
|
591
|
-
super(
|
|
591
|
+
super(errorMessage2);
|
|
592
592
|
}
|
|
593
593
|
};
|
|
594
594
|
function assert(val, validator) {
|
|
@@ -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.11" ;
|
|
1060
1060
|
AGENT = `keynv-cli/${VERSION}`;
|
|
1061
1061
|
}
|
|
1062
1062
|
});
|
|
@@ -1695,15 +1695,15 @@ var init_parseUtil = __esm({
|
|
|
1695
1695
|
message: issueData.message
|
|
1696
1696
|
};
|
|
1697
1697
|
}
|
|
1698
|
-
let
|
|
1698
|
+
let errorMessage2 = "";
|
|
1699
1699
|
const maps = errorMaps.filter((m) => !!m).slice().reverse();
|
|
1700
1700
|
for (const map of maps) {
|
|
1701
|
-
|
|
1701
|
+
errorMessage2 = map(fullIssue, { data, defaultError: errorMessage2 }).message;
|
|
1702
1702
|
}
|
|
1703
1703
|
return {
|
|
1704
1704
|
...issueData,
|
|
1705
1705
|
path: fullPath,
|
|
1706
|
-
message:
|
|
1706
|
+
message: errorMessage2
|
|
1707
1707
|
};
|
|
1708
1708
|
};
|
|
1709
1709
|
EMPTY_PATH = [];
|
|
@@ -5604,6 +5604,7 @@ function buildUrl(base, path, query) {
|
|
|
5604
5604
|
return url.toString();
|
|
5605
5605
|
}
|
|
5606
5606
|
async function tryRefresh(creds) {
|
|
5607
|
+
if (creds.auth_kind === "cli_token" || creds.refresh_token.length === 0) return null;
|
|
5607
5608
|
const res = await fetch(buildUrl(creds.server_url, "/v1/auth/refresh"), {
|
|
5608
5609
|
method: "POST",
|
|
5609
5610
|
headers: { "content-type": "application/json", "x-keynv-agent": AGENT },
|
|
@@ -5748,7 +5749,7 @@ function parseEnvFile(content, filename) {
|
|
|
5748
5749
|
`invalid key '${name}' (must match /^[A-Za-z_][A-Za-z0-9_]*$/)`
|
|
5749
5750
|
);
|
|
5750
5751
|
}
|
|
5751
|
-
|
|
5752
|
+
const valueRaw = body.slice(eq + 1);
|
|
5752
5753
|
let value;
|
|
5753
5754
|
const valueLeading = valueRaw.replace(/^\s+/, "");
|
|
5754
5755
|
const firstCh = valueLeading.charAt(0);
|
|
@@ -5854,19 +5855,6 @@ var init_envFile = __esm({
|
|
|
5854
5855
|
}
|
|
5855
5856
|
});
|
|
5856
5857
|
|
|
5857
|
-
// src/ui/helpers/tty.ts
|
|
5858
|
-
var tty_exports = {};
|
|
5859
|
-
__export(tty_exports, {
|
|
5860
|
-
isInteractive: () => isInteractive
|
|
5861
|
-
});
|
|
5862
|
-
function isInteractive() {
|
|
5863
|
-
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
5864
|
-
}
|
|
5865
|
-
var init_tty = __esm({
|
|
5866
|
-
"src/ui/helpers/tty.ts"() {
|
|
5867
|
-
}
|
|
5868
|
-
});
|
|
5869
|
-
|
|
5870
5858
|
// ../../node_modules/.pnpm/fast-string-truncated-width@3.0.3/node_modules/fast-string-truncated-width/dist/utils.js
|
|
5871
5859
|
var getCodePointsLength, isFullWidth, isWideNotCJKTNotEmoji;
|
|
5872
5860
|
var init_utils = __esm({
|
|
@@ -7021,6 +7009,95 @@ ${c2}
|
|
|
7021
7009
|
} }).prompt();
|
|
7022
7010
|
}
|
|
7023
7011
|
});
|
|
7012
|
+
function aiContextBody() {
|
|
7013
|
+
return `## keynv (secrets)
|
|
7014
|
+
|
|
7015
|
+
This project uses [keynv](https://keynv.dev) for secrets. There is a \`.keynv.env\` file at the project root containing **alias references** like \`OPENAI_API_KEY=@projectname.dev.openai-key\` \u2014 these are NOT real values, they are references to secrets stored in the keynv vault.
|
|
7016
|
+
|
|
7017
|
+
### Mental model
|
|
7018
|
+
|
|
7019
|
+
- **\`.keynv.env\`** \u2014 checked into git, contains aliases. Safe to read, edit, commit.
|
|
7020
|
+
- **Vault** \u2014 holds the real values. The \`keynv\` CLI reads it on demand.
|
|
7021
|
+
- **\`keynv exec -- <command>\`** \u2014 forks the command in a subprocess where the real values are injected into env vars. The parent process (where you, the AI agent, run) NEVER sees the real values.
|
|
7022
|
+
- **Redactor** \u2014 pipes subprocess stdout/stderr through a masker before they reach this terminal. Even if a script accidentally echoes a secret, you see \`<REDACTED:...>\` here.
|
|
7023
|
+
|
|
7024
|
+
### What you should do
|
|
7025
|
+
|
|
7026
|
+
When the user asks about secrets or env vars, follow this decision tree:
|
|
7027
|
+
|
|
7028
|
+
| User intent | Run / suggest |
|
|
7029
|
+
|---|---|
|
|
7030
|
+
| "Add a new API key" / "set up a secret" | \`keynv secret create\` (interactive prompt picks project + env + key) |
|
|
7031
|
+
| "I have a \`.env\`, migrate it" | \`keynv init\` (you're seeing this file because that already happened) |
|
|
7032
|
+
| "Run the app / dev server / tests" | \`keynv exec -- <their existing command>\` (auto-loads \`.keynv.env\`) |
|
|
7033
|
+
| "Show me the value of X" | \`keynv secret get @alias\` \u2014 but copy to clipboard, do NOT print the value into chat / terminal |
|
|
7034
|
+
| "Rotate this key" | \`keynv secret rotate @alias\` |
|
|
7035
|
+
| "Add a new env var to the project" | Edit \`.keynv.env\` and add \`NAME=@project.env.key\` (after creating the secret with \`keynv secret create\`) |
|
|
7036
|
+
| "Who has access?" | \`keynv member list <project>\` |
|
|
7037
|
+
|
|
7038
|
+
### Hard rules \u2014 do not violate
|
|
7039
|
+
|
|
7040
|
+
1. **Never print resolved secret values** to the chat, terminal, log, or any file. The user's editor / scrollback / chat history is an attack surface.
|
|
7041
|
+
2. **Never write a \`.env\` file** (or any file) containing real secret values. The whole point of keynv is that those don't exist on disk.
|
|
7042
|
+
3. **Never edit \`.keynv.env\` to substitute resolved values for aliases.** The file should only ever contain \`@project.env.key\` references and plain non-secret literals.
|
|
7043
|
+
4. **Use \`keynv exec --\` to run commands that need env vars.** Don't try to load \`.keynv.env\` yourself by parsing it manually; the wrapper handles resolution + redaction + subprocess isolation.
|
|
7044
|
+
5. **If a tool fails with "blocked by keynv-guard" or similar**, do NOT try to work around it. Tell the user \u2014 that's a sign of a misconfiguration, not an obstacle to bypass.
|
|
7045
|
+
6. **Treat the alias literal (\`@project.env.key\`) as the canonical reference**, never the resolved value. When you write code, configs, or commands that need a secret, write the alias.
|
|
7046
|
+
|
|
7047
|
+
### Quick reference
|
|
7048
|
+
|
|
7049
|
+
\`\`\`bash
|
|
7050
|
+
keynv # interactive TUI menu (pick projects, secrets, members)
|
|
7051
|
+
keynv exec -- <cmd> # run cmd with .keynv.env loaded
|
|
7052
|
+
keynv secret create # walk through creating a new secret
|
|
7053
|
+
keynv secret list <project> # list aliases (no values)
|
|
7054
|
+
keynv whoami # who am I logged in as
|
|
7055
|
+
keynv --help # full command list
|
|
7056
|
+
\`\`\`
|
|
7057
|
+
|
|
7058
|
+
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\`.`;
|
|
7059
|
+
}
|
|
7060
|
+
function renderKeynvBlock() {
|
|
7061
|
+
return `${KEYNV_BLOCK_START}
|
|
7062
|
+
${aiContextBody()}
|
|
7063
|
+
${KEYNV_BLOCK_END}`;
|
|
7064
|
+
}
|
|
7065
|
+
function writeAiContext(rootPath) {
|
|
7066
|
+
const path = join(rootPath, AGENTS_FILE_BASENAME);
|
|
7067
|
+
const block = renderKeynvBlock();
|
|
7068
|
+
if (!existsSync(path)) {
|
|
7069
|
+
const initial = `# Agent guidance for this project
|
|
7070
|
+
|
|
7071
|
+
${block}
|
|
7072
|
+
`;
|
|
7073
|
+
writeFileSync(path, initial);
|
|
7074
|
+
return "created";
|
|
7075
|
+
}
|
|
7076
|
+
const existing = readFileSync(path, "utf8");
|
|
7077
|
+
const startIdx = existing.indexOf(KEYNV_BLOCK_START);
|
|
7078
|
+
const endIdx = existing.indexOf(KEYNV_BLOCK_END);
|
|
7079
|
+
if (startIdx >= 0 && endIdx > startIdx) {
|
|
7080
|
+
const before = existing.slice(0, startIdx);
|
|
7081
|
+
const after = existing.slice(endIdx + KEYNV_BLOCK_END.length);
|
|
7082
|
+
const next = `${before}${block}${after}`;
|
|
7083
|
+
if (next === existing) return "unchanged";
|
|
7084
|
+
writeFileSync(path, next);
|
|
7085
|
+
return "updated";
|
|
7086
|
+
}
|
|
7087
|
+
const sep = existing.length === 0 ? "" : existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
7088
|
+
const trailingNewline = existing.endsWith("\n") ? "" : "\n";
|
|
7089
|
+
writeFileSync(path, `${existing}${trailingNewline}${sep}${block}
|
|
7090
|
+
`);
|
|
7091
|
+
return "appended";
|
|
7092
|
+
}
|
|
7093
|
+
var AGENTS_FILE_BASENAME, KEYNV_BLOCK_START, KEYNV_BLOCK_END;
|
|
7094
|
+
var init_aiContext = __esm({
|
|
7095
|
+
"src/init/aiContext.ts"() {
|
|
7096
|
+
AGENTS_FILE_BASENAME = "AGENTS.md";
|
|
7097
|
+
KEYNV_BLOCK_START = "<!-- keynv:start -- generated by `keynv init`; safe to leave intact, will be refreshed on re-run -->";
|
|
7098
|
+
KEYNV_BLOCK_END = "<!-- keynv:end -->";
|
|
7099
|
+
}
|
|
7100
|
+
});
|
|
7024
7101
|
function findProjectRoot(startDir) {
|
|
7025
7102
|
let dir = resolve(startDir);
|
|
7026
7103
|
for (let i = 0; i < 64; i++) {
|
|
@@ -7051,16 +7128,20 @@ function buildRoot(dir, marker) {
|
|
|
7051
7128
|
}
|
|
7052
7129
|
if (pkg.scripts && typeof pkg.scripts === "object") {
|
|
7053
7130
|
scripts = Object.fromEntries(
|
|
7054
|
-
Object.entries(pkg.scripts).filter(
|
|
7055
|
-
([, v2]) => typeof v2 === "string"
|
|
7056
|
-
)
|
|
7131
|
+
Object.entries(pkg.scripts).filter(([, v2]) => typeof v2 === "string")
|
|
7057
7132
|
);
|
|
7058
7133
|
}
|
|
7059
7134
|
} catch {
|
|
7060
7135
|
invalid = true;
|
|
7061
7136
|
}
|
|
7062
7137
|
}
|
|
7063
|
-
return {
|
|
7138
|
+
return {
|
|
7139
|
+
path: dir,
|
|
7140
|
+
suggestedName,
|
|
7141
|
+
marker,
|
|
7142
|
+
packageJsonScripts: scripts,
|
|
7143
|
+
packageJsonInvalid: invalid
|
|
7144
|
+
};
|
|
7064
7145
|
}
|
|
7065
7146
|
function findEnvFiles(rootDir) {
|
|
7066
7147
|
let entries;
|
|
@@ -7134,95 +7215,6 @@ var init_detect = __esm({
|
|
|
7134
7215
|
KEYNV_ENV_BASENAME = ".keynv.env";
|
|
7135
7216
|
}
|
|
7136
7217
|
});
|
|
7137
|
-
function aiContextBody() {
|
|
7138
|
-
return `## keynv (secrets)
|
|
7139
|
-
|
|
7140
|
-
This project uses [keynv](https://keynv.dev) for secrets. There is a \`.keynv.env\` file at the project root containing **alias references** like \`OPENAI_API_KEY=@projectname.dev.openai-key\` \u2014 these are NOT real values, they are references to secrets stored in the keynv vault.
|
|
7141
|
-
|
|
7142
|
-
### Mental model
|
|
7143
|
-
|
|
7144
|
-
- **\`.keynv.env\`** \u2014 checked into git, contains aliases. Safe to read, edit, commit.
|
|
7145
|
-
- **Vault** \u2014 holds the real values. The \`keynv\` CLI reads it on demand.
|
|
7146
|
-
- **\`keynv exec -- <command>\`** \u2014 forks the command in a subprocess where the real values are injected into env vars. The parent process (where you, the AI agent, run) NEVER sees the real values.
|
|
7147
|
-
- **Redactor** \u2014 pipes subprocess stdout/stderr through a masker before they reach this terminal. Even if a script accidentally echoes a secret, you see \`<REDACTED:...>\` here.
|
|
7148
|
-
|
|
7149
|
-
### What you should do
|
|
7150
|
-
|
|
7151
|
-
When the user asks about secrets or env vars, follow this decision tree:
|
|
7152
|
-
|
|
7153
|
-
| User intent | Run / suggest |
|
|
7154
|
-
|---|---|
|
|
7155
|
-
| "Add a new API key" / "set up a secret" | \`keynv secret create\` (interactive prompt picks project + env + key) |
|
|
7156
|
-
| "I have a \`.env\`, migrate it" | \`keynv init\` (you're seeing this file because that already happened) |
|
|
7157
|
-
| "Run the app / dev server / tests" | \`keynv exec -- <their existing command>\` (auto-loads \`.keynv.env\`) |
|
|
7158
|
-
| "Show me the value of X" | \`keynv secret get @alias\` \u2014 but copy to clipboard, do NOT print the value into chat / terminal |
|
|
7159
|
-
| "Rotate this key" | \`keynv secret rotate @alias\` |
|
|
7160
|
-
| "Add a new env var to the project" | Edit \`.keynv.env\` and add \`NAME=@project.env.key\` (after creating the secret with \`keynv secret create\`) |
|
|
7161
|
-
| "Who has access?" | \`keynv member list <project>\` |
|
|
7162
|
-
|
|
7163
|
-
### Hard rules \u2014 do not violate
|
|
7164
|
-
|
|
7165
|
-
1. **Never print resolved secret values** to the chat, terminal, log, or any file. The user's editor / scrollback / chat history is an attack surface.
|
|
7166
|
-
2. **Never write a \`.env\` file** (or any file) containing real secret values. The whole point of keynv is that those don't exist on disk.
|
|
7167
|
-
3. **Never edit \`.keynv.env\` to substitute resolved values for aliases.** The file should only ever contain \`@project.env.key\` references and plain non-secret literals.
|
|
7168
|
-
4. **Use \`keynv exec --\` to run commands that need env vars.** Don't try to load \`.keynv.env\` yourself by parsing it manually; the wrapper handles resolution + redaction + subprocess isolation.
|
|
7169
|
-
5. **If a tool fails with "blocked by keynv-guard" or similar**, do NOT try to work around it. Tell the user \u2014 that's a sign of a misconfiguration, not an obstacle to bypass.
|
|
7170
|
-
6. **Treat the alias literal (\`@project.env.key\`) as the canonical reference**, never the resolved value. When you write code, configs, or commands that need a secret, write the alias.
|
|
7171
|
-
|
|
7172
|
-
### Quick reference
|
|
7173
|
-
|
|
7174
|
-
\`\`\`bash
|
|
7175
|
-
keynv # interactive TUI menu (pick projects, secrets, members)
|
|
7176
|
-
keynv exec -- <cmd> # run cmd with .keynv.env loaded
|
|
7177
|
-
keynv secret create # walk through creating a new secret
|
|
7178
|
-
keynv secret list <project> # list aliases (no values)
|
|
7179
|
-
keynv whoami # who am I logged in as
|
|
7180
|
-
keynv --help # full command list
|
|
7181
|
-
\`\`\`
|
|
7182
|
-
|
|
7183
|
-
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\`.`;
|
|
7184
|
-
}
|
|
7185
|
-
function renderKeynvBlock() {
|
|
7186
|
-
return `${KEYNV_BLOCK_START}
|
|
7187
|
-
${aiContextBody()}
|
|
7188
|
-
${KEYNV_BLOCK_END}`;
|
|
7189
|
-
}
|
|
7190
|
-
function writeAiContext(rootPath) {
|
|
7191
|
-
const path = join(rootPath, AGENTS_FILE_BASENAME);
|
|
7192
|
-
const block = renderKeynvBlock();
|
|
7193
|
-
if (!existsSync(path)) {
|
|
7194
|
-
const initial = `# Agent guidance for this project
|
|
7195
|
-
|
|
7196
|
-
${block}
|
|
7197
|
-
`;
|
|
7198
|
-
writeFileSync(path, initial);
|
|
7199
|
-
return "created";
|
|
7200
|
-
}
|
|
7201
|
-
const existing = readFileSync(path, "utf8");
|
|
7202
|
-
const startIdx = existing.indexOf(KEYNV_BLOCK_START);
|
|
7203
|
-
const endIdx = existing.indexOf(KEYNV_BLOCK_END);
|
|
7204
|
-
if (startIdx >= 0 && endIdx > startIdx) {
|
|
7205
|
-
const before = existing.slice(0, startIdx);
|
|
7206
|
-
const after = existing.slice(endIdx + KEYNV_BLOCK_END.length);
|
|
7207
|
-
const next = `${before}${block}${after}`;
|
|
7208
|
-
if (next === existing) return "unchanged";
|
|
7209
|
-
writeFileSync(path, next);
|
|
7210
|
-
return "updated";
|
|
7211
|
-
}
|
|
7212
|
-
const sep = existing.length === 0 ? "" : existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
|
|
7213
|
-
const trailingNewline = existing.endsWith("\n") ? "" : "\n";
|
|
7214
|
-
writeFileSync(path, `${existing}${trailingNewline}${sep}${block}
|
|
7215
|
-
`);
|
|
7216
|
-
return "appended";
|
|
7217
|
-
}
|
|
7218
|
-
var AGENTS_FILE_BASENAME, KEYNV_BLOCK_START, KEYNV_BLOCK_END;
|
|
7219
|
-
var init_aiContext = __esm({
|
|
7220
|
-
"src/init/aiContext.ts"() {
|
|
7221
|
-
AGENTS_FILE_BASENAME = "AGENTS.md";
|
|
7222
|
-
KEYNV_BLOCK_START = "<!-- keynv:start -- generated by `keynv init`; safe to leave intact, will be refreshed on re-run -->";
|
|
7223
|
-
KEYNV_BLOCK_END = "<!-- keynv:end -->";
|
|
7224
|
-
}
|
|
7225
|
-
});
|
|
7226
7218
|
|
|
7227
7219
|
// src/init/heuristics.ts
|
|
7228
7220
|
function shannonEntropyBits(s) {
|
|
@@ -7641,7 +7633,9 @@ ${detail}`
|
|
|
7641
7633
|
}
|
|
7642
7634
|
const totalEntries = [...perEnv.values()].reduce((n, arr) => n + arr.length, 0);
|
|
7643
7635
|
if (totalEntries === 0) {
|
|
7644
|
-
R2.info(
|
|
7636
|
+
R2.info(
|
|
7637
|
+
"All env files were empty or only contained framework-managed vars. Nothing to upload."
|
|
7638
|
+
);
|
|
7645
7639
|
ye("Done.");
|
|
7646
7640
|
return { exitCode: 0 };
|
|
7647
7641
|
}
|
|
@@ -7693,10 +7687,10 @@ ${detail}`
|
|
|
7693
7687
|
const planSummary = [
|
|
7694
7688
|
`Project: ${projectChoice.name}${projectChoice.created ? " (will be created)" : ""}`,
|
|
7695
7689
|
`Environments: ${distinctEnvs.join(", ")}`,
|
|
7696
|
-
|
|
7690
|
+
"Per-env breakdown:",
|
|
7697
7691
|
perEnvCounts,
|
|
7698
7692
|
`Script wraps: ${scriptWrapSelection.length}`,
|
|
7699
|
-
|
|
7693
|
+
"Original .env: delete after upload",
|
|
7700
7694
|
opts.dryRun ? "Dry-run: no changes will be made." : ""
|
|
7701
7695
|
].filter(Boolean).join("\n");
|
|
7702
7696
|
Se(planSummary, "About to apply");
|
|
@@ -7731,7 +7725,11 @@ ${detail}`
|
|
|
7731
7725
|
method: "POST",
|
|
7732
7726
|
body: { env: env2, key: aliasKey, value: e2.value }
|
|
7733
7727
|
});
|
|
7734
|
-
const alias2 = buildAlias({
|
|
7728
|
+
const alias2 = buildAlias({
|
|
7729
|
+
project: projectChoice.name,
|
|
7730
|
+
environment: env2,
|
|
7731
|
+
key: aliasKey
|
|
7732
|
+
});
|
|
7735
7733
|
if (alias2 === null) {
|
|
7736
7734
|
failed.push({
|
|
7737
7735
|
env: env2,
|
|
@@ -7753,7 +7751,9 @@ ${detail}`
|
|
|
7753
7751
|
if (failed.length === 0) {
|
|
7754
7752
|
s.stop(`Uploaded ${totalToUpload} secret${totalToUpload === 1 ? "" : "s"}`);
|
|
7755
7753
|
} else {
|
|
7756
|
-
s.error(
|
|
7754
|
+
s.error(
|
|
7755
|
+
`${totalToUpload - failed.length}/${totalToUpload} uploaded; ${failed.length} failed`
|
|
7756
|
+
);
|
|
7757
7757
|
for (const f of failed) R2.warn(` [${f.env}] ${f.name}: ${f.reason}`);
|
|
7758
7758
|
}
|
|
7759
7759
|
}
|
|
@@ -7968,7 +7968,9 @@ async function ensureProjectAndEnvs(client, projectChoice, distinctEnvs) {
|
|
|
7968
7968
|
body: envBodyFor(envName)
|
|
7969
7969
|
});
|
|
7970
7970
|
} catch (err) {
|
|
7971
|
-
s.error(
|
|
7971
|
+
s.error(
|
|
7972
|
+
`Failed to add env "${envName}": ${err instanceof Error ? err.message : String(err)}`
|
|
7973
|
+
);
|
|
7972
7974
|
return null;
|
|
7973
7975
|
}
|
|
7974
7976
|
}
|
|
@@ -8014,7 +8016,7 @@ function composeKeynvEnv(opts) {
|
|
|
8014
8016
|
}
|
|
8015
8017
|
}
|
|
8016
8018
|
if (mergeWithExisting !== null) {
|
|
8017
|
-
lines.push(
|
|
8019
|
+
lines.push("# <<< keynv init <<<");
|
|
8018
8020
|
}
|
|
8019
8021
|
return lines;
|
|
8020
8022
|
}
|
|
@@ -8035,11 +8037,11 @@ function updatePackageJsonScripts(path, originalScripts, selectedNames) {
|
|
|
8035
8037
|
}
|
|
8036
8038
|
var init_init = __esm({
|
|
8037
8039
|
"src/ui/flows/init.ts"() {
|
|
8038
|
-
init_dist();
|
|
8039
8040
|
init_dist5();
|
|
8041
|
+
init_dist();
|
|
8040
8042
|
init_envFile();
|
|
8041
|
-
init_detect();
|
|
8042
8043
|
init_aiContext();
|
|
8044
|
+
init_detect();
|
|
8043
8045
|
init_heuristics();
|
|
8044
8046
|
init_scriptWrap();
|
|
8045
8047
|
init_cancel();
|
|
@@ -8047,30 +8049,103 @@ var init_init = __esm({
|
|
|
8047
8049
|
}
|
|
8048
8050
|
});
|
|
8049
8051
|
|
|
8050
|
-
// src/ui/helpers/
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8052
|
+
// src/ui/helpers/tty.ts
|
|
8053
|
+
var tty_exports = {};
|
|
8054
|
+
__export(tty_exports, {
|
|
8055
|
+
isInteractive: () => isInteractive
|
|
8056
|
+
});
|
|
8057
|
+
function isInteractive() {
|
|
8058
|
+
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
8056
8059
|
}
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8060
|
+
var init_tty = __esm({
|
|
8061
|
+
"src/ui/helpers/tty.ts"() {
|
|
8062
|
+
}
|
|
8063
|
+
});
|
|
8064
|
+
function sleep(ms) {
|
|
8065
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
8066
|
+
}
|
|
8067
|
+
async function errorMessage(res, fallback) {
|
|
8068
|
+
const text = await res.text();
|
|
8069
|
+
if (!text) return fallback;
|
|
8070
|
+
try {
|
|
8071
|
+
const parsed = JSON.parse(text);
|
|
8072
|
+
return parsed.error?.message ?? fallback;
|
|
8073
|
+
} catch {
|
|
8074
|
+
return fallback;
|
|
8075
|
+
}
|
|
8076
|
+
}
|
|
8077
|
+
function openBrowser(url) {
|
|
8078
|
+
try {
|
|
8079
|
+
const os = platform();
|
|
8080
|
+
const command = os === "win32" ? "cmd" : os === "darwin" ? "open" : "xdg-open";
|
|
8081
|
+
const args = os === "win32" ? ["/c", "start", "", url] : [url];
|
|
8082
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
8083
|
+
child.unref();
|
|
8084
|
+
return true;
|
|
8085
|
+
} catch {
|
|
8086
|
+
return false;
|
|
8087
|
+
}
|
|
8088
|
+
}
|
|
8089
|
+
async function runBrowserAuth(serverUrl) {
|
|
8090
|
+
const startRes = await fetch(new URL("/v1/auth/cli/browser/start", serverUrl).toString(), {
|
|
8091
|
+
method: "POST",
|
|
8092
|
+
headers: { "content-type": "application/json", "x-keynv-agent": AGENT },
|
|
8093
|
+
body: JSON.stringify({ device_name: hostname() })
|
|
8067
8094
|
});
|
|
8068
|
-
if (
|
|
8069
|
-
|
|
8095
|
+
if (!startRes.ok) {
|
|
8096
|
+
throw new BrowserAuthError(
|
|
8097
|
+
await errorMessage(startRes, `Browser auth failed to start (${startRes.status}).`)
|
|
8098
|
+
);
|
|
8099
|
+
}
|
|
8100
|
+
const start = await startRes.json();
|
|
8101
|
+
const opened = openBrowser(start.verification_uri_complete);
|
|
8102
|
+
if (!opened) {
|
|
8103
|
+
throw new BrowserAuthError(`Open this URL in your browser: ${start.verification_uri_complete}`);
|
|
8104
|
+
}
|
|
8105
|
+
const deadline = Date.now() + start.expires_in * 1e3;
|
|
8106
|
+
const intervalMs = Math.max(1, start.interval) * 1e3;
|
|
8107
|
+
while (Date.now() < deadline) {
|
|
8108
|
+
await sleep(intervalMs);
|
|
8109
|
+
const pollRes = await fetch(new URL("/v1/auth/cli/browser/poll", serverUrl).toString(), {
|
|
8110
|
+
method: "POST",
|
|
8111
|
+
headers: { "content-type": "application/json", "x-keynv-agent": AGENT },
|
|
8112
|
+
body: JSON.stringify({ device_code: start.device_code })
|
|
8113
|
+
});
|
|
8114
|
+
if (pollRes.status === 202) continue;
|
|
8115
|
+
if (!pollRes.ok) {
|
|
8116
|
+
throw new BrowserAuthError(
|
|
8117
|
+
await errorMessage(pollRes, `Browser auth failed (${pollRes.status}).`)
|
|
8118
|
+
);
|
|
8119
|
+
}
|
|
8120
|
+
const data = await pollRes.json();
|
|
8121
|
+
return {
|
|
8122
|
+
auth_kind: "session",
|
|
8123
|
+
server_url: serverUrl,
|
|
8124
|
+
user_id: data.user.id,
|
|
8125
|
+
email: data.user.email,
|
|
8126
|
+
org_id: data.user.org_id,
|
|
8127
|
+
org_role: data.user.org_role,
|
|
8128
|
+
access_token: data.access_token,
|
|
8129
|
+
refresh_token: data.refresh_token,
|
|
8130
|
+
access_expires_at: new Date(Date.now() + data.expires_in * 1e3).toISOString()
|
|
8131
|
+
};
|
|
8132
|
+
}
|
|
8133
|
+
throw new BrowserAuthError("Browser auth timed out. Run `keynv login` to try again.");
|
|
8070
8134
|
}
|
|
8071
|
-
var
|
|
8072
|
-
|
|
8073
|
-
|
|
8135
|
+
var BrowserAuthError;
|
|
8136
|
+
var init_browser_auth = __esm({
|
|
8137
|
+
"src/client/browser-auth.ts"() {
|
|
8138
|
+
init_version();
|
|
8139
|
+
BrowserAuthError = class extends Error {
|
|
8140
|
+
};
|
|
8141
|
+
}
|
|
8142
|
+
});
|
|
8143
|
+
|
|
8144
|
+
// src/client/defaults.ts
|
|
8145
|
+
var DEFAULT_SERVER_URL;
|
|
8146
|
+
var init_defaults = __esm({
|
|
8147
|
+
"src/client/defaults.ts"() {
|
|
8148
|
+
DEFAULT_SERVER_URL = "https://api.keynv.dev";
|
|
8074
8149
|
}
|
|
8075
8150
|
});
|
|
8076
8151
|
|
|
@@ -8098,6 +8173,33 @@ var init_pickEnv = __esm({
|
|
|
8098
8173
|
init_dist5();
|
|
8099
8174
|
}
|
|
8100
8175
|
});
|
|
8176
|
+
|
|
8177
|
+
// src/ui/helpers/pickSecret.ts
|
|
8178
|
+
async function listSecrets(client, projectId2) {
|
|
8179
|
+
const data = await client.request(
|
|
8180
|
+
`/v1/projects/${projectId2}/secrets`
|
|
8181
|
+
);
|
|
8182
|
+
return data.secrets;
|
|
8183
|
+
}
|
|
8184
|
+
async function pickSecret(client, projectId2, message = "Secret:") {
|
|
8185
|
+
const secrets = await listSecrets(client, projectId2);
|
|
8186
|
+
if (secrets.length === 0) return null;
|
|
8187
|
+
const value = await Ee({
|
|
8188
|
+
message,
|
|
8189
|
+
options: secrets.map((s) => ({
|
|
8190
|
+
value: s.alias,
|
|
8191
|
+
label: s.alias,
|
|
8192
|
+
hint: `v${s.version}`
|
|
8193
|
+
}))
|
|
8194
|
+
});
|
|
8195
|
+
if (q(value)) return null;
|
|
8196
|
+
return secrets.find((s) => s.alias === value) ?? null;
|
|
8197
|
+
}
|
|
8198
|
+
var init_pickSecret = __esm({
|
|
8199
|
+
"src/ui/helpers/pickSecret.ts"() {
|
|
8200
|
+
init_dist5();
|
|
8201
|
+
}
|
|
8202
|
+
});
|
|
8101
8203
|
async function runSecretsFlow(client, project) {
|
|
8102
8204
|
let target = project;
|
|
8103
8205
|
if (!target) {
|
|
@@ -8161,7 +8263,10 @@ async function runSecretMenu(client, project, alias2) {
|
|
|
8161
8263
|
}
|
|
8162
8264
|
} else if (choice === "rotate") {
|
|
8163
8265
|
const newValue = unwrap(
|
|
8164
|
-
await Ce({
|
|
8266
|
+
await Ce({
|
|
8267
|
+
message: "New value",
|
|
8268
|
+
validate: (v2) => v2?.length ? void 0 : "required"
|
|
8269
|
+
})
|
|
8165
8270
|
);
|
|
8166
8271
|
const data = await client.request(`${path}/rotate`, {
|
|
8167
8272
|
method: "POST",
|
|
@@ -8169,9 +8274,7 @@ async function runSecretMenu(client, project, alias2) {
|
|
|
8169
8274
|
});
|
|
8170
8275
|
R2.success(`Rotated ${data.alias} \u2192 v${data.version}`);
|
|
8171
8276
|
} else if (choice === "delete") {
|
|
8172
|
-
const confirmed = unwrap(
|
|
8173
|
-
await ue({ message: `Delete ${alias2}?`, initialValue: false })
|
|
8174
|
-
);
|
|
8277
|
+
const confirmed = unwrap(await ue({ message: `Delete ${alias2}?`, initialValue: false }));
|
|
8175
8278
|
if (!confirmed) continue;
|
|
8176
8279
|
await client.request(path, { method: "DELETE" });
|
|
8177
8280
|
R2.success(`Deleted ${alias2}`);
|
|
@@ -8180,11 +8283,13 @@ async function runSecretMenu(client, project, alias2) {
|
|
|
8180
8283
|
}
|
|
8181
8284
|
}
|
|
8182
8285
|
async function copyToClipboard(value) {
|
|
8183
|
-
const
|
|
8184
|
-
const cmd =
|
|
8286
|
+
const platform2 = process.platform;
|
|
8287
|
+
const cmd = platform2 === "darwin" ? ["pbcopy", []] : platform2 === "win32" ? ["clip", []] : ["xclip", ["-selection", "clipboard"]];
|
|
8185
8288
|
return new Promise((resolve3) => {
|
|
8186
8289
|
try {
|
|
8187
|
-
const child = spawn(cmd[0], cmd[1], {
|
|
8290
|
+
const child = spawn(cmd[0], cmd[1], {
|
|
8291
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
8292
|
+
});
|
|
8188
8293
|
child.on("error", () => resolve3(false));
|
|
8189
8294
|
child.on("exit", (code) => resolve3(code === 0));
|
|
8190
8295
|
child.stdin.end(value);
|
|
@@ -8211,7 +8316,7 @@ async function promptNewSecret(client, project) {
|
|
|
8211
8316
|
}),
|
|
8212
8317
|
value: () => Ce({
|
|
8213
8318
|
message: "Value (hidden)",
|
|
8214
|
-
validate: (v2) => v2
|
|
8319
|
+
validate: (v2) => v2?.length ? void 0 : "required"
|
|
8215
8320
|
})
|
|
8216
8321
|
},
|
|
8217
8322
|
{
|
|
@@ -8240,8 +8345,8 @@ async function createSecretInteractive(client, project) {
|
|
|
8240
8345
|
}
|
|
8241
8346
|
var init_secret = __esm({
|
|
8242
8347
|
"src/ui/flows/secret.ts"() {
|
|
8243
|
-
init_dist();
|
|
8244
8348
|
init_dist5();
|
|
8349
|
+
init_dist();
|
|
8245
8350
|
init_cancel();
|
|
8246
8351
|
init_pickEnv();
|
|
8247
8352
|
init_pickProject();
|
|
@@ -28980,11 +29085,11 @@ var require_pg_connection_string = __commonJS({
|
|
|
28980
29085
|
config.client_encoding = result.searchParams.get("encoding");
|
|
28981
29086
|
return config;
|
|
28982
29087
|
}
|
|
28983
|
-
const
|
|
29088
|
+
const hostname2 = dummyHost ? "" : result.hostname;
|
|
28984
29089
|
if (!config.host) {
|
|
28985
|
-
config.host = decodeURIComponent(
|
|
28986
|
-
} else if (
|
|
28987
|
-
result.pathname =
|
|
29090
|
+
config.host = decodeURIComponent(hostname2);
|
|
29091
|
+
} else if (hostname2 && /^%2f/i.test(hostname2)) {
|
|
29092
|
+
result.pathname = hostname2 + result.pathname;
|
|
28988
29093
|
}
|
|
28989
29094
|
if (!config.port) {
|
|
28990
29095
|
config.port = result.port;
|
|
@@ -40090,9 +40195,9 @@ var require_cluster = __commonJS({
|
|
|
40090
40195
|
}
|
|
40091
40196
|
});
|
|
40092
40197
|
}
|
|
40093
|
-
resolveSrv(
|
|
40198
|
+
resolveSrv(hostname2) {
|
|
40094
40199
|
return new Promise((resolve3, reject) => {
|
|
40095
|
-
this.options.resolveSrv(
|
|
40200
|
+
this.options.resolveSrv(hostname2, (err, records) => {
|
|
40096
40201
|
if (err) {
|
|
40097
40202
|
return reject(err);
|
|
40098
40203
|
}
|
|
@@ -40114,14 +40219,14 @@ var require_cluster = __commonJS({
|
|
|
40114
40219
|
});
|
|
40115
40220
|
});
|
|
40116
40221
|
}
|
|
40117
|
-
dnsLookup(
|
|
40222
|
+
dnsLookup(hostname2) {
|
|
40118
40223
|
return new Promise((resolve3, reject) => {
|
|
40119
|
-
this.options.dnsLookup(
|
|
40224
|
+
this.options.dnsLookup(hostname2, (err, address) => {
|
|
40120
40225
|
if (err) {
|
|
40121
|
-
debug2("failed to resolve hostname %s to IP: %s",
|
|
40226
|
+
debug2("failed to resolve hostname %s to IP: %s", hostname2, err.message);
|
|
40122
40227
|
reject(err);
|
|
40123
40228
|
} else {
|
|
40124
|
-
debug2("resolved hostname %s to IP %s",
|
|
40229
|
+
debug2("resolved hostname %s to IP %s", hostname2, address);
|
|
40125
40230
|
resolve3(address);
|
|
40126
40231
|
}
|
|
40127
40232
|
});
|
|
@@ -42446,85 +42551,27 @@ var init_audit2 = __esm({
|
|
|
42446
42551
|
});
|
|
42447
42552
|
|
|
42448
42553
|
// src/ui/flows/login.ts
|
|
42449
|
-
async function runLoginFlow(client) {
|
|
42450
|
-
const
|
|
42451
|
-
{
|
|
42452
|
-
server: () => Re({
|
|
42453
|
-
message: "Server URL",
|
|
42454
|
-
placeholder: "http://localhost:8080",
|
|
42455
|
-
defaultValue: "http://localhost:8080"
|
|
42456
|
-
}),
|
|
42457
|
-
email: () => Re({
|
|
42458
|
-
message: "Email",
|
|
42459
|
-
validate: (v2) => v2 && v2.includes("@") ? void 0 : "enter an email"
|
|
42460
|
-
}),
|
|
42461
|
-
password: () => Ce({
|
|
42462
|
-
message: "Password",
|
|
42463
|
-
validate: (v2) => v2 && v2.length >= 1 ? void 0 : "required"
|
|
42464
|
-
})
|
|
42465
|
-
},
|
|
42466
|
-
{
|
|
42467
|
-
onCancel: () => {
|
|
42468
|
-
throw new UserCancelled();
|
|
42469
|
-
}
|
|
42470
|
-
}
|
|
42471
|
-
);
|
|
42472
|
-
const server = answers.server || "http://localhost:8080";
|
|
42554
|
+
async function runLoginFlow(client, options = {}) {
|
|
42555
|
+
const server = options.server ?? DEFAULT_SERVER_URL;
|
|
42473
42556
|
const s = ft();
|
|
42474
|
-
s.start("
|
|
42475
|
-
let
|
|
42557
|
+
s.start("Opening browser for keynv login");
|
|
42558
|
+
let creds;
|
|
42476
42559
|
try {
|
|
42477
|
-
|
|
42478
|
-
method: "POST",
|
|
42479
|
-
headers: { "content-type": "application/json", "x-keynv-agent": AGENT },
|
|
42480
|
-
body: JSON.stringify({ email: answers.email, password: answers.password })
|
|
42481
|
-
});
|
|
42560
|
+
creds = await runBrowserAuth(server);
|
|
42482
42561
|
} catch (err) {
|
|
42483
|
-
s.error("
|
|
42484
|
-
me(err instanceof Error ? err.message : "
|
|
42562
|
+
s.error("Browser login failed");
|
|
42563
|
+
me(err instanceof Error ? err.message : "Unable to authenticate.");
|
|
42485
42564
|
return false;
|
|
42486
42565
|
}
|
|
42487
|
-
|
|
42488
|
-
|
|
42489
|
-
try {
|
|
42490
|
-
const parsed = await res.json();
|
|
42491
|
-
msg = parsed.error?.message ?? msg;
|
|
42492
|
-
} catch {
|
|
42493
|
-
}
|
|
42494
|
-
s.error(msg);
|
|
42495
|
-
return false;
|
|
42496
|
-
}
|
|
42497
|
-
const data = await res.json();
|
|
42498
|
-
await saveCredentials({
|
|
42499
|
-
server_url: server,
|
|
42500
|
-
user_id: data.user.id,
|
|
42501
|
-
email: data.user.email,
|
|
42502
|
-
org_id: data.user.org_id,
|
|
42503
|
-
org_role: data.user.org_role,
|
|
42504
|
-
access_token: data.access_token,
|
|
42505
|
-
refresh_token: data.refresh_token,
|
|
42506
|
-
access_expires_at: new Date(Date.now() + data.expires_in * 1e3).toISOString()
|
|
42507
|
-
});
|
|
42508
|
-
await client.setCredentials({
|
|
42509
|
-
server_url: server,
|
|
42510
|
-
user_id: data.user.id,
|
|
42511
|
-
email: data.user.email,
|
|
42512
|
-
org_id: data.user.org_id,
|
|
42513
|
-
org_role: data.user.org_role,
|
|
42514
|
-
access_token: data.access_token,
|
|
42515
|
-
refresh_token: data.refresh_token,
|
|
42516
|
-
access_expires_at: new Date(Date.now() + data.expires_in * 1e3).toISOString()
|
|
42517
|
-
});
|
|
42518
|
-
s.stop(`Logged in as ${data.user.email}`);
|
|
42566
|
+
await client.setCredentials(creds);
|
|
42567
|
+
s.stop(`Logged in as ${creds.email}`);
|
|
42519
42568
|
return true;
|
|
42520
42569
|
}
|
|
42521
42570
|
var init_login = __esm({
|
|
42522
42571
|
"src/ui/flows/login.ts"() {
|
|
42523
42572
|
init_dist5();
|
|
42524
|
-
|
|
42525
|
-
|
|
42526
|
-
init_version();
|
|
42527
|
-
init_cancel();
|
|
42573
|
+
init_browser_auth();
|
|
42574
|
+
init_defaults();
|
|
42528
42575
|
}
|
|
42529
42576
|
});
|
|
42530
42577
|
|
|
@@ -42583,7 +42630,7 @@ async function addMemberInteractive(client, project) {
|
|
|
42583
42630
|
{
|
|
42584
42631
|
email: () => Re({
|
|
42585
42632
|
message: "Email",
|
|
42586
|
-
validate: (v2) => v2
|
|
42633
|
+
validate: (v2) => v2?.includes("@") ? void 0 : "enter an email"
|
|
42587
42634
|
}),
|
|
42588
42635
|
role: () => Ee({
|
|
42589
42636
|
message: "Role",
|
|
@@ -42682,8 +42729,11 @@ function printDetail(detail) {
|
|
|
42682
42729
|
const envLines = detail.environments.map(
|
|
42683
42730
|
(e2) => ` ${e2.name} (tier=${e2.tier}, approval=${e2.require_approval})`
|
|
42684
42731
|
);
|
|
42685
|
-
Se(
|
|
42686
|
-
|
|
42732
|
+
Se(
|
|
42733
|
+
`${detail.name} (${detail.id})
|
|
42734
|
+
${envLines.join("\n") || " (no environments)"}`,
|
|
42735
|
+
"Project"
|
|
42736
|
+
);
|
|
42687
42737
|
}
|
|
42688
42738
|
async function createProjectInteractive(client) {
|
|
42689
42739
|
const answers = await he(
|
|
@@ -42742,6 +42792,7 @@ async function runMenu() {
|
|
|
42742
42792
|
ge(`keynv ${VERSION}`);
|
|
42743
42793
|
const client = new ApiClient();
|
|
42744
42794
|
await client.ensureHydrated();
|
|
42795
|
+
let didLogin = false;
|
|
42745
42796
|
if (!client.isLoggedIn) {
|
|
42746
42797
|
R2.info("Not logged in.");
|
|
42747
42798
|
try {
|
|
@@ -42750,6 +42801,7 @@ async function runMenu() {
|
|
|
42750
42801
|
ye("Login cancelled.");
|
|
42751
42802
|
return 1;
|
|
42752
42803
|
}
|
|
42804
|
+
didLogin = true;
|
|
42753
42805
|
} catch (err) {
|
|
42754
42806
|
if (err instanceof UserCancelled) {
|
|
42755
42807
|
me("Login cancelled.");
|
|
@@ -42761,6 +42813,16 @@ async function runMenu() {
|
|
|
42761
42813
|
const u = client.currentUser;
|
|
42762
42814
|
if (u) R2.message(`${u.email} (${u.org_role}) @ ${u.server_url}`);
|
|
42763
42815
|
}
|
|
42816
|
+
if (didLogin) {
|
|
42817
|
+
const setup = await ue({
|
|
42818
|
+
message: "Set up this project now?",
|
|
42819
|
+
initialValue: true
|
|
42820
|
+
});
|
|
42821
|
+
if (!q(setup) && setup) {
|
|
42822
|
+
const { runInitFlow: runInitFlow2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
42823
|
+
await runInitFlow2(client, { cwd: process.cwd(), dryRun: false, noScripts: false });
|
|
42824
|
+
}
|
|
42825
|
+
}
|
|
42764
42826
|
while (true) {
|
|
42765
42827
|
let choice;
|
|
42766
42828
|
try {
|
|
@@ -42828,14 +42890,14 @@ var init_menu = __esm({
|
|
|
42828
42890
|
init_dist5();
|
|
42829
42891
|
init_http();
|
|
42830
42892
|
init_store();
|
|
42831
|
-
|
|
42832
|
-
init_cancel();
|
|
42893
|
+
init_version();
|
|
42833
42894
|
init_audit2();
|
|
42834
42895
|
init_login();
|
|
42835
42896
|
init_member();
|
|
42836
42897
|
init_project();
|
|
42837
42898
|
init_secret();
|
|
42838
|
-
|
|
42899
|
+
init_format();
|
|
42900
|
+
init_cancel();
|
|
42839
42901
|
}
|
|
42840
42902
|
});
|
|
42841
42903
|
|
|
@@ -43516,11 +43578,11 @@ var reducers = {
|
|
|
43516
43578
|
return { ...state, options: [{ name: `-c`, value: String(command) }] };
|
|
43517
43579
|
}
|
|
43518
43580
|
},
|
|
43519
|
-
setError: (state, segment, segmentIndex,
|
|
43581
|
+
setError: (state, segment, segmentIndex, errorMessage2) => {
|
|
43520
43582
|
if (segment === SpecialToken.EndOfInput || segment === SpecialToken.EndOfPartialInput) {
|
|
43521
|
-
return { ...state, errorMessage: `${
|
|
43583
|
+
return { ...state, errorMessage: `${errorMessage2}.` };
|
|
43522
43584
|
} else {
|
|
43523
|
-
return { ...state, errorMessage: `${
|
|
43585
|
+
return { ...state, errorMessage: `${errorMessage2} ("${segment}").` };
|
|
43524
43586
|
}
|
|
43525
43587
|
},
|
|
43526
43588
|
setOptionArityError: (state, segment) => {
|
|
@@ -45026,10 +45088,7 @@ to load a specific file, or set KEYNV_ENV_FILE in the environment.
|
|
|
45026
45088
|
spelled \`--from\` to avoid the collision.)
|
|
45027
45089
|
`,
|
|
45028
45090
|
examples: [
|
|
45029
|
-
[
|
|
45030
|
-
"Auto-load .keynv.env from cwd or parents",
|
|
45031
|
-
"$0 exec -- next dev"
|
|
45032
|
-
],
|
|
45091
|
+
["Auto-load .keynv.env from cwd or parents", "$0 exec -- next dev"],
|
|
45033
45092
|
[
|
|
45034
45093
|
"Run mysql with the alias substituted at fork-exec time",
|
|
45035
45094
|
"$0 exec -- mysql -p@billing.dev.db_pass -h db.example.com"
|
|
@@ -45210,9 +45269,9 @@ function signalNumber(sig) {
|
|
|
45210
45269
|
|
|
45211
45270
|
// src/commands/init.ts
|
|
45212
45271
|
init_http();
|
|
45213
|
-
init_tty();
|
|
45214
45272
|
init_init();
|
|
45215
45273
|
init_cancel();
|
|
45274
|
+
init_tty();
|
|
45216
45275
|
var InitCommand = class extends Command {
|
|
45217
45276
|
static paths = [["init"]];
|
|
45218
45277
|
static usage = Command.Usage({
|
|
@@ -45273,6 +45332,8 @@ commands directly.
|
|
|
45273
45332
|
};
|
|
45274
45333
|
|
|
45275
45334
|
// src/commands/login.ts
|
|
45335
|
+
init_browser_auth();
|
|
45336
|
+
init_defaults();
|
|
45276
45337
|
init_http();
|
|
45277
45338
|
init_store();
|
|
45278
45339
|
init_format();
|
|
@@ -45333,18 +45394,40 @@ var LoginCommand = class extends Command {
|
|
|
45333
45394
|
static usage = Command.Usage({
|
|
45334
45395
|
description: "Authenticate against a keynv server.",
|
|
45335
45396
|
examples: [
|
|
45336
|
-
["
|
|
45397
|
+
["Browser login", "$0 login"],
|
|
45398
|
+
["Self-hosted browser login", "$0 login --server http://localhost:8080"],
|
|
45399
|
+
["Headless token login", "$0 login --server http://... --token kt_..."],
|
|
45337
45400
|
["Non-interactive", "$0 login --server http://... --email a@b.com --password ..."]
|
|
45338
45401
|
]
|
|
45339
45402
|
});
|
|
45340
45403
|
server = options_exports.String("--server", { description: "Server base URL." });
|
|
45404
|
+
token = options_exports.String("--token", { description: "CLI token for headless auth." });
|
|
45341
45405
|
email = options_exports.String("--email", { description: "Email address." });
|
|
45342
45406
|
password = options_exports.String("--password", {
|
|
45343
45407
|
description: "Password (use stdin to avoid argv leak)."
|
|
45344
45408
|
});
|
|
45345
45409
|
async execute() {
|
|
45346
|
-
const
|
|
45347
|
-
|
|
45410
|
+
const finalServerUrl = this.server ?? DEFAULT_SERVER_URL;
|
|
45411
|
+
if (this.token) {
|
|
45412
|
+
return this.loginWithToken(finalServerUrl, this.token);
|
|
45413
|
+
}
|
|
45414
|
+
if (!this.email && !this.password) {
|
|
45415
|
+
this.context.stdout.write(`Opening browser for ${finalServerUrl} ...
|
|
45416
|
+
`);
|
|
45417
|
+
try {
|
|
45418
|
+
const creds = await runBrowserAuth(finalServerUrl);
|
|
45419
|
+
await saveCredentials(creds);
|
|
45420
|
+
this.context.stdout.write(`logged in as ${creds.email} (${creds.org_role})
|
|
45421
|
+
`);
|
|
45422
|
+
return 0;
|
|
45423
|
+
} catch (err) {
|
|
45424
|
+
this.context.stderr.write(
|
|
45425
|
+
`keynv: ${err instanceof Error ? err.message : "browser login failed"}
|
|
45426
|
+
`
|
|
45427
|
+
);
|
|
45428
|
+
return 1;
|
|
45429
|
+
}
|
|
45430
|
+
}
|
|
45348
45431
|
const email2 = this.email ?? await promptLine("email: ");
|
|
45349
45432
|
const password = this.password ?? await promptHidden("password: ");
|
|
45350
45433
|
const res = await fetch(new URL("/v1/auth/login", finalServerUrl).toString(), {
|
|
@@ -45375,6 +45458,7 @@ var LoginCommand = class extends Command {
|
|
|
45375
45458
|
const data = await res.json();
|
|
45376
45459
|
try {
|
|
45377
45460
|
await saveCredentials({
|
|
45461
|
+
auth_kind: "session",
|
|
45378
45462
|
server_url: finalServerUrl,
|
|
45379
45463
|
user_id: data.user.id,
|
|
45380
45464
|
email: data.user.email,
|
|
@@ -45392,6 +45476,54 @@ var LoginCommand = class extends Command {
|
|
|
45392
45476
|
return 1;
|
|
45393
45477
|
}
|
|
45394
45478
|
this.context.stdout.write(`logged in as ${data.user.email} (${data.user.org_role})
|
|
45479
|
+
`);
|
|
45480
|
+
return 0;
|
|
45481
|
+
}
|
|
45482
|
+
async loginWithToken(serverUrl, token) {
|
|
45483
|
+
const res = await fetch(new URL("/v1/whoami", serverUrl).toString(), {
|
|
45484
|
+
headers: { authorization: `Bearer ${token}`, "x-keynv-agent": AGENT }
|
|
45485
|
+
});
|
|
45486
|
+
if (!res.ok) {
|
|
45487
|
+
const text = await res.text();
|
|
45488
|
+
let msg = `token login failed (${res.status})`;
|
|
45489
|
+
try {
|
|
45490
|
+
const parsed = JSON.parse(text);
|
|
45491
|
+
msg = parsed.error?.message ?? msg;
|
|
45492
|
+
this.context.stderr.write(
|
|
45493
|
+
`${fmtError({
|
|
45494
|
+
status: res.status,
|
|
45495
|
+
...parsed.error?.code ? { code: parsed.error.code } : {},
|
|
45496
|
+
message: msg
|
|
45497
|
+
})}
|
|
45498
|
+
`
|
|
45499
|
+
);
|
|
45500
|
+
} catch {
|
|
45501
|
+
this.context.stderr.write(`keynv: ${msg}
|
|
45502
|
+
`);
|
|
45503
|
+
}
|
|
45504
|
+
return 1;
|
|
45505
|
+
}
|
|
45506
|
+
const data = await res.json();
|
|
45507
|
+
try {
|
|
45508
|
+
await saveCredentials({
|
|
45509
|
+
auth_kind: "cli_token",
|
|
45510
|
+
server_url: serverUrl,
|
|
45511
|
+
user_id: data.id,
|
|
45512
|
+
email: data.email,
|
|
45513
|
+
org_id: data.org_id,
|
|
45514
|
+
org_role: data.org_role,
|
|
45515
|
+
access_token: token,
|
|
45516
|
+
refresh_token: "",
|
|
45517
|
+
access_expires_at: "9999-12-31T23:59:59.999Z"
|
|
45518
|
+
});
|
|
45519
|
+
} catch (err) {
|
|
45520
|
+
this.context.stderr.write(
|
|
45521
|
+
`keynv: failed to persist credentials: ${err instanceof Error ? err.message : String(err)}
|
|
45522
|
+
`
|
|
45523
|
+
);
|
|
45524
|
+
return 1;
|
|
45525
|
+
}
|
|
45526
|
+
this.context.stdout.write(`logged in as ${data.email} (${data.org_role})
|
|
45395
45527
|
`);
|
|
45396
45528
|
return 0;
|
|
45397
45529
|
}
|
|
@@ -45716,12 +45848,12 @@ streaming-mode limitation in @keynv/redactor).
|
|
|
45716
45848
|
// src/commands/secret.ts
|
|
45717
45849
|
init_dist();
|
|
45718
45850
|
init_http();
|
|
45851
|
+
init_secret();
|
|
45719
45852
|
init_format();
|
|
45720
|
-
init_tty();
|
|
45721
45853
|
init_cancel();
|
|
45722
45854
|
init_pickProject();
|
|
45723
45855
|
init_pickSecret();
|
|
45724
|
-
|
|
45856
|
+
init_tty();
|
|
45725
45857
|
async function findProjectIdByName2(client, name) {
|
|
45726
45858
|
const data = await client.request("/v1/projects");
|
|
45727
45859
|
const match = data.projects.find((p2) => p2.name === name);
|
|
@@ -46466,8 +46598,8 @@ the error message.
|
|
|
46466
46598
|
};
|
|
46467
46599
|
|
|
46468
46600
|
// src/commands/ui.ts
|
|
46469
|
-
init_menu();
|
|
46470
46601
|
init_tty();
|
|
46602
|
+
init_menu();
|
|
46471
46603
|
var UICommand = class extends Command {
|
|
46472
46604
|
static paths = [["ui"]];
|
|
46473
46605
|
static usage = Command.Usage({
|