@keynv/cli 0.1.0-rc.8 → 0.1.0-rc.9

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
@@ -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.8" ;
1059
+ VERSION = "0.1.0-rc.9" ;
1060
1060
  AGENT = `keynv-cli/${VERSION}`;
1061
1061
  }
1062
1062
  });
@@ -7128,6 +7128,95 @@ var init_detect = __esm({
7128
7128
  KEYNV_ENV_BASENAME = ".keynv.env";
7129
7129
  }
7130
7130
  });
7131
+ function aiContextBody() {
7132
+ return `## keynv (secrets)
7133
+
7134
+ 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.
7135
+
7136
+ ### Mental model
7137
+
7138
+ - **\`.keynv.env\`** \u2014 checked into git, contains aliases. Safe to read, edit, commit.
7139
+ - **Vault** \u2014 holds the real values. The \`keynv\` CLI reads it on demand.
7140
+ - **\`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.
7141
+ - **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.
7142
+
7143
+ ### What you should do
7144
+
7145
+ When the user asks about secrets or env vars, follow this decision tree:
7146
+
7147
+ | User intent | Run / suggest |
7148
+ |---|---|
7149
+ | "Add a new API key" / "set up a secret" | \`keynv secret create\` (interactive prompt picks project + env + key) |
7150
+ | "I have a \`.env\`, migrate it" | \`keynv init\` (you're seeing this file because that already happened) |
7151
+ | "Run the app / dev server / tests" | \`keynv exec -- <their existing command>\` (auto-loads \`.keynv.env\`) |
7152
+ | "Show me the value of X" | \`keynv secret get @alias\` \u2014 but copy to clipboard, do NOT print the value into chat / terminal |
7153
+ | "Rotate this key" | \`keynv secret rotate @alias\` |
7154
+ | "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\`) |
7155
+ | "Who has access?" | \`keynv member list <project>\` |
7156
+
7157
+ ### Hard rules \u2014 do not violate
7158
+
7159
+ 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.
7160
+ 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.
7161
+ 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.
7162
+ 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.
7163
+ 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.
7164
+ 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.
7165
+
7166
+ ### Quick reference
7167
+
7168
+ \`\`\`bash
7169
+ keynv # interactive TUI menu (pick projects, secrets, members)
7170
+ keynv exec -- <cmd> # run cmd with .keynv.env loaded
7171
+ keynv secret create # walk through creating a new secret
7172
+ keynv secret list <project> # list aliases (no values)
7173
+ keynv whoami # who am I logged in as
7174
+ keynv --help # full command list
7175
+ \`\`\`
7176
+
7177
+ 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\`.`;
7178
+ }
7179
+ function renderKeynvBlock() {
7180
+ return `${KEYNV_BLOCK_START}
7181
+ ${aiContextBody()}
7182
+ ${KEYNV_BLOCK_END}`;
7183
+ }
7184
+ function writeAiContext(rootPath) {
7185
+ const path = join(rootPath, AGENTS_FILE_BASENAME);
7186
+ const block = renderKeynvBlock();
7187
+ if (!existsSync(path)) {
7188
+ const initial = `# Agent guidance for this project
7189
+
7190
+ ${block}
7191
+ `;
7192
+ writeFileSync(path, initial);
7193
+ return "created";
7194
+ }
7195
+ const existing = readFileSync(path, "utf8");
7196
+ const startIdx = existing.indexOf(KEYNV_BLOCK_START);
7197
+ const endIdx = existing.indexOf(KEYNV_BLOCK_END);
7198
+ if (startIdx >= 0 && endIdx > startIdx) {
7199
+ const before = existing.slice(0, startIdx);
7200
+ const after = existing.slice(endIdx + KEYNV_BLOCK_END.length);
7201
+ const next = `${before}${block}${after}`;
7202
+ if (next === existing) return "unchanged";
7203
+ writeFileSync(path, next);
7204
+ return "updated";
7205
+ }
7206
+ const sep = existing.length === 0 ? "" : existing.endsWith("\n\n") ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
7207
+ const trailingNewline = existing.endsWith("\n") ? "" : "\n";
7208
+ writeFileSync(path, `${existing}${trailingNewline}${sep}${block}
7209
+ `);
7210
+ return "appended";
7211
+ }
7212
+ var AGENTS_FILE_BASENAME, KEYNV_BLOCK_START, KEYNV_BLOCK_END;
7213
+ var init_aiContext = __esm({
7214
+ "src/init/aiContext.ts"() {
7215
+ AGENTS_FILE_BASENAME = "AGENTS.md";
7216
+ KEYNV_BLOCK_START = "<!-- keynv:start -- generated by `keynv init`; safe to leave intact, will be refreshed on re-run -->";
7217
+ KEYNV_BLOCK_END = "<!-- keynv:end -->";
7218
+ }
7219
+ });
7131
7220
 
7132
7221
  // src/init/heuristics.ts
7133
7222
  function shannonEntropyBits(s) {
@@ -7675,6 +7764,14 @@ async function runInitFlow(client, opts) {
7675
7764
  R2.error(`Failed to write .keynv.env: ${err instanceof Error ? err.message : String(err)}`);
7676
7765
  return { exitCode: 1 };
7677
7766
  }
7767
+ try {
7768
+ const outcome = writeAiContext(root.path);
7769
+ if (outcome === "created") R2.success("Wrote AGENTS.md (so AI agents understand keynv)");
7770
+ else if (outcome === "updated") R2.success("Refreshed keynv section in AGENTS.md");
7771
+ else if (outcome === "appended") R2.success("Appended keynv section to AGENTS.md");
7772
+ } catch (err) {
7773
+ R2.warn(`Could not write AGENTS.md: ${err instanceof Error ? err.message : String(err)}`);
7774
+ }
7678
7775
  if (scriptWrapSelection.length > 0 && root.packageJsonScripts) {
7679
7776
  try {
7680
7777
  updatePackageJsonScripts(
@@ -7869,6 +7966,7 @@ var init_init = __esm({
7869
7966
  init_dist5();
7870
7967
  init_envFile();
7871
7968
  init_detect();
7969
+ init_aiContext();
7872
7970
  init_heuristics();
7873
7971
  init_scriptWrap();
7874
7972
  init_cancel();
@@ -24764,7 +24862,7 @@ var require_named_placeholders = __commonJS({
24764
24862
  }
24765
24863
  return s;
24766
24864
  }
24767
- function join5(tree) {
24865
+ function join6(tree) {
24768
24866
  if (tree.length === 1) {
24769
24867
  return tree;
24770
24868
  }
@@ -24790,7 +24888,7 @@ var require_named_placeholders = __commonJS({
24790
24888
  if (cache2 && (tree = cache2.get(query))) {
24791
24889
  return toArrayParams(tree, paramsObj);
24792
24890
  }
24793
- tree = join5(parse(query));
24891
+ tree = join6(parse(query));
24794
24892
  if (cache2) {
24795
24893
  cache2.set(query, tree);
24796
24894
  }