@carllee1983/dbcli 1.56.0 → 1.57.0

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/core.mjs CHANGED
@@ -1798,6 +1798,20 @@ Reference: {referencePath}`,
1798
1798
  add_enum_description: "Create an enum type (PostgreSQL only)",
1799
1799
  alter_enum_description: "Add a value to an enum type",
1800
1800
  drop_enum_description: "Drop an enum type"
1801
+ },
1802
+ password: {
1803
+ description: "Change the password of a single connection, leaving every other setting untouched",
1804
+ arg_connection: "Connection name (defaults to the current default connection)",
1805
+ opt_stdin: "Read the new password from stdin (for rotation scripts)",
1806
+ opt_password: "New password given inline (visible in shell history and process list)",
1807
+ opt_skip_test: "Skip verifying the new password against the database before saving",
1808
+ prompt: "New password for connection '{name}'",
1809
+ source_conflict: "--password and --stdin cannot be used together; pick one source.",
1810
+ needs_source: "No terminal available for a masked prompt. Pass the value with --stdin or --password.",
1811
+ empty: "The new password is empty; nothing was changed.",
1812
+ updated: "Password for connection '{name}' updated \u2192 {envFile} ({varName})",
1813
+ converted: 'The literal password in config.json was replaced by a {{ "$env": "{varName}" }} reference; later rotations only touch the env file.',
1814
+ skipped_test: "Verification skipped (--skip-test): the new password was not checked against the database."
1801
1815
  }
1802
1816
  };
1803
1817
  // resources/lang/zh-TW/messages.json
@@ -2078,6 +2092,20 @@ var messages_default2 = {
2078
2092
  add_enum_description: "\u5EFA\u7ACB\u5217\u8209\u578B\u5225\uFF08\u50C5 PostgreSQL\uFF09",
2079
2093
  alter_enum_description: "\u65B0\u589E\u5217\u8209\u503C",
2080
2094
  drop_enum_description: "\u522A\u9664\u5217\u8209\u578B\u5225"
2095
+ },
2096
+ password: {
2097
+ description: "\u53EA\u8B8A\u66F4\u55AE\u4E00\u9023\u7DDA\u7684\u5BC6\u78BC\uFF0C\u5176\u9918\u8A2D\u5B9A\u539F\u5C01\u4E0D\u52D5",
2098
+ arg_connection: "\u9023\u7DDA\u540D\u7A31\uFF08\u7701\u7565\u6642\u6539\u9810\u8A2D\u9023\u7DDA\uFF09",
2099
+ opt_stdin: "\u5F9E stdin \u8B80\u53D6\u65B0\u5BC6\u78BC\uFF08\u4F9B\u8F2A\u66FF\u8173\u672C\u4F7F\u7528\uFF09",
2100
+ opt_password: "\u76F4\u63A5\u5E36\u5165\u65B0\u5BC6\u78BC\uFF08\u6703\u7559\u5728 shell \u6B77\u53F2\u8207\u884C\u7A0B\u5217\u8868\uFF09",
2101
+ opt_skip_test: "\u5B58\u6A94\u524D\u4E0D\u5148\u7528\u65B0\u5BC6\u78BC\u9023\u7DDA\u9A57\u8B49",
2102
+ prompt: "\u9023\u7DDA '{name}' \u7684\u65B0\u5BC6\u78BC",
2103
+ source_conflict: "--password \u8207 --stdin \u53EA\u80FD\u64C7\u4E00\u3002",
2104
+ needs_source: "\u6C92\u6709\u7D42\u7AEF\u6A5F\u53EF\u505A\u906E\u853D\u8F38\u5165\uFF0C\u8ACB\u6539\u7528 --stdin \u6216 --password \u50B3\u5165\u3002",
2105
+ empty: "\u65B0\u5BC6\u78BC\u662F\u7A7A\u7684\uFF0C\u672A\u505A\u4EFB\u4F55\u8B8A\u66F4\u3002",
2106
+ updated: "\u5DF2\u66F4\u65B0\u9023\u7DDA '{name}' \u7684\u5BC6\u78BC \u2192 {envFile}\uFF08{varName}\uFF09",
2107
+ converted: 'config.json \u88E1\u7684\u660E\u6587\u5BC6\u78BC\u5DF2\u6539\u6210 {{ "$env": "{varName}" }} \u53C3\u7167\uFF0C\u4E4B\u5F8C\u8F2A\u66FF\u53EA\u6703\u52D5 env \u6A94\u3002',
2108
+ skipped_test: "\u5DF2\u8DF3\u904E\u9A57\u8B49\uFF08--skip-test\uFF09\uFF1A\u65B0\u5BC6\u78BC\u672A\u7D93\u8CC7\u6599\u5EAB\u78BA\u8A8D\u3002"
2081
2109
  }
2082
2110
  };
2083
2111
  // resources/lang/en/shell.json
@@ -6017,6 +6045,13 @@ async function select(message, choices) {
6017
6045
  return await selectFallback(message, choices);
6018
6046
  return await inquirer.select({ message, choices });
6019
6047
  }
6048
+ async function secret(message) {
6049
+ const inquirer = await loadInquirer();
6050
+ if (!inquirer) {
6051
+ throw new Error("Masked input is unavailable; pass the value with --stdin or --password instead.");
6052
+ }
6053
+ return await inquirer.password({ message, mask: "*" });
6054
+ }
6020
6055
  async function confirm(message) {
6021
6056
  if (!process.stdin.isTTY)
6022
6057
  return await confirmFallback(message);
@@ -6028,7 +6063,8 @@ async function confirm(message) {
6028
6063
  var promptUser = {
6029
6064
  text,
6030
6065
  select,
6031
- confirm
6066
+ confirm,
6067
+ secret
6032
6068
  };
6033
6069
 
6034
6070
  // src/types/blacklist.ts
@@ -10755,43 +10791,11 @@ function listConnections(config) {
10755
10791
  });
10756
10792
  }
10757
10793
  // src/core/config-v2-mutations.ts
10758
- import { join as join8 } from "path";
10759
10794
  var SQL_SYSTEMS = ["postgresql", "mysql", "mariadb"];
10760
10795
  function envVarNameFor(connName, field) {
10761
10796
  const slug = connName.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
10762
10797
  return `DBCLI_${slug}_${field.toUpperCase()}`;
10763
10798
  }
10764
- async function writeConnectionSecret(projectPath, connName, field, value) {
10765
- assertConfigMutationApproved();
10766
- if (value.includes(`
10767
- `) || value.includes("\r")) {
10768
- throw new Error("secret value \u4E0D\u53EF\u5305\u542B\u63DB\u884C\u5B57\u5143");
10769
- }
10770
- const config = await readV2Config(projectPath);
10771
- const conn = config.connections[connName];
10772
- if (!conn)
10773
- throw new Error(`\u9023\u7DDA '${connName}' \u4E0D\u5B58\u5728`);
10774
- const storagePath = await resolveConfigStoragePath(projectPath);
10775
- const envFile = conn.envFile ?? `.env.${connName}`;
10776
- const envPath = join8(storagePath, envFile);
10777
- const varName = envVarNameFor(connName, field);
10778
- let content = "";
10779
- const file = Bun.file(envPath);
10780
- if (await file.exists())
10781
- content = await file.text();
10782
- const line = `${varName}=${value}`;
10783
- const re = new RegExp(`^${varName}=.*$`, "m");
10784
- if (re.test(content)) {
10785
- content = content.replace(re, line);
10786
- } else {
10787
- content = content.length && !content.endsWith(`
10788
- `) ? `${content}
10789
- ${line}
10790
- ` : `${content}${line}
10791
- `;
10792
- }
10793
- await Bun.write(envPath, content);
10794
- }
10795
10799
  function removeConnection(config, name) {
10796
10800
  if (!(name in config.connections))
10797
10801
  throw new Error(`\u9023\u7DDA '${name}' \u4E0D\u5B58\u5728`);
@@ -10853,6 +10857,135 @@ function upsertConnection(config, input) {
10853
10857
  connections: { ...config.connections, [input.name]: connection }
10854
10858
  };
10855
10859
  }
10860
+ // src/core/connection-credential.ts
10861
+ import { join as join8 } from "path";
10862
+
10863
+ // src/core/env-file-writer.ts
10864
+ import { chmod as chmod2, mkdir as mkdir3, readFile as readFile2, writeFile } from "fs/promises";
10865
+ import { dirname as dirname2 } from "path";
10866
+ var SECRET_FILE_MODE = 384;
10867
+ function escapeForRegExp(value) {
10868
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10869
+ }
10870
+ async function upsertEnvVar(envPath, varName, value) {
10871
+ let content = "";
10872
+ try {
10873
+ content = await readFile2(envPath, "utf8");
10874
+ } catch (cause) {
10875
+ const code = cause.code;
10876
+ if (code !== "ENOENT")
10877
+ throw cause;
10878
+ await mkdir3(dirname2(envPath), { recursive: true });
10879
+ }
10880
+ const line = `${varName}="${value}"`;
10881
+ const existing = new RegExp(`^${escapeForRegExp(varName)}=.*$`, "m");
10882
+ if (existing.test(content)) {
10883
+ content = content.replace(existing, () => line);
10884
+ } else {
10885
+ content = content.length && !content.endsWith(`
10886
+ `) ? `${content}
10887
+ ${line}
10888
+ ` : `${content}${line}
10889
+ `;
10890
+ }
10891
+ await writeFile(envPath, content, { mode: SECRET_FILE_MODE });
10892
+ await chmod2(envPath, SECRET_FILE_MODE);
10893
+ }
10894
+
10895
+ // src/core/connection-credential.ts
10896
+ var DEFAULT_ENV_FILE = ".env.local";
10897
+ var V1_VAR_NAME = "DBCLI_PASSWORD";
10898
+ var V1_CONNECTION_NAME = "default";
10899
+ async function readRawConfig(storagePath) {
10900
+ const configPath = join8(storagePath, "config.json");
10901
+ const file = Bun.file(configPath);
10902
+ if (!await file.exists()) {
10903
+ throw new ConfigError(`\u627E\u4E0D\u5230\u8A2D\u5B9A\u6A94\uFF1A${configPath}`);
10904
+ }
10905
+ return JSON.parse(await file.text());
10906
+ }
10907
+ function envRefName(value) {
10908
+ if (typeof value === "object" && value !== null && "$env" in value) {
10909
+ return String(value.$env);
10910
+ }
10911
+ return;
10912
+ }
10913
+ async function resolvePasswordTarget(projectPath, connectionName) {
10914
+ const storagePath = await resolveConfigStoragePath(projectPath);
10915
+ const raw = await readRawConfig(storagePath);
10916
+ if (detectConfigVersion(raw) !== 2) {
10917
+ if (connectionName && connectionName !== V1_CONNECTION_NAME) {
10918
+ throw new ConfigError(`\u9019\u662F v1 \u8A2D\u5B9A\uFF0C\u53EA\u6709\u4E00\u689D\u9023\u7DDA\uFF0C\u7121\u6CD5\u6307\u5B9A\u9023\u7DDA\u540D\u7A31 '${connectionName}'\u3002` + `\u9700\u8981\u591A\u9023\u7DDA\u8ACB\u5148\u7528 dbcli init --conn-name <name> \u5347\u7D1A\u5230 v2\u3002`);
10919
+ }
10920
+ const connection2 = raw.connection ?? {};
10921
+ const customRef = envRefName(connection2.password);
10922
+ if (customRef !== undefined && customRef !== V1_VAR_NAME) {
10923
+ throw new ConfigError(`\u9019\u500B v1 \u8A2D\u5B9A\u7684\u5BC6\u78BC\u4F86\u81EA\u74B0\u5883\u8B8A\u6578 '${customRef}'\uFF0Cdbcli \u7121\u6CD5\u4EE3\u70BA\u8F2A\u66FF\u3002` + `\u8ACB\u76F4\u63A5\u66F4\u65B0\u8A72\u74B0\u5883\u8B8A\u6578\uFF0C\u6216\u7528 dbcli init --conn-name <name> \u5347\u7D1A\u5230 v2 \u5F8C\u518D\u8F2A\u66FF\u3002`);
10924
+ }
10925
+ return {
10926
+ connection: V1_CONNECTION_NAME,
10927
+ envFile: DEFAULT_ENV_FILE,
10928
+ varName: V1_VAR_NAME,
10929
+ convertedToEnvRef: false
10930
+ };
10931
+ }
10932
+ const config = await readV2Config(projectPath);
10933
+ const targetName = connectionName ?? config.default;
10934
+ const connection = config.connections[targetName];
10935
+ if (!connection) {
10936
+ throw new ConfigError(connectionNotFoundMessage(targetName, Object.keys(config.connections)));
10937
+ }
10938
+ const referencedVar = envRefName(connection.password);
10939
+ return {
10940
+ connection: targetName,
10941
+ envFile: connection.envFile ?? DEFAULT_ENV_FILE,
10942
+ varName: referencedVar ?? envVarNameFor(targetName, "password"),
10943
+ convertedToEnvRef: referencedVar === undefined
10944
+ };
10945
+ }
10946
+ function assertSingleLine(value) {
10947
+ if (value.includes(`
10948
+ `) || value.includes("\r")) {
10949
+ throw new ConfigError("\u5BC6\u78BC\u4E0D\u53EF\u5305\u542B\u63DB\u884C\u5B57\u5143");
10950
+ }
10951
+ }
10952
+ async function stripV1LiteralPassword(storagePath, raw) {
10953
+ const connection = raw.connection ?? {};
10954
+ if (typeof connection.password !== "string")
10955
+ return;
10956
+ const stripped = { ...raw, connection: { ...connection } };
10957
+ delete stripped.connection.password;
10958
+ await writeConfigWithIntegrity(storagePath, JSON.stringify(stripped, null, 2));
10959
+ }
10960
+ async function setConnectionPassword(projectPath, connectionName, password) {
10961
+ assertConfigMutationApproved();
10962
+ assertSingleLine(password);
10963
+ const target = await resolvePasswordTarget(projectPath, connectionName);
10964
+ const storagePath = await resolveConfigStoragePath(projectPath);
10965
+ const raw = await readRawConfig(storagePath);
10966
+ await upsertEnvVar(join8(storagePath, target.envFile), target.varName, password);
10967
+ if (detectConfigVersion(raw) !== 2) {
10968
+ await stripV1LiteralPassword(storagePath, raw);
10969
+ return target;
10970
+ }
10971
+ const config = await readV2Config(projectPath);
10972
+ const connection = config.connections[target.connection];
10973
+ const needsEnvFile = connection.envFile === undefined;
10974
+ if (target.convertedToEnvRef || needsEnvFile) {
10975
+ await writeV2Config(projectPath, {
10976
+ ...config,
10977
+ connections: {
10978
+ ...config.connections,
10979
+ [target.connection]: {
10980
+ ...connection,
10981
+ envFile: target.envFile,
10982
+ ...target.convertedToEnvRef && { password: { $env: target.varName } }
10983
+ }
10984
+ }
10985
+ });
10986
+ }
10987
+ return target;
10988
+ }
10856
10989
  // src/utils/config-error-format.ts
10857
10990
  var SUPPORTED_SYSTEMS = [
10858
10991
  "postgresql",
@@ -10938,7 +11071,7 @@ function isZodError(error) {
10938
11071
 
10939
11072
  // src/core/config.ts
10940
11073
  import { join as join9 } from "path";
10941
- import { mkdir as mkdir3 } from "fs/promises";
11074
+ import { mkdir as mkdir4 } from "fs/promises";
10942
11075
  // src/agent-core/env-ref.ts
10943
11076
  function resolveEnvRef(value, fieldName, env = process.env) {
10944
11077
  if (typeof value === "string")
@@ -11008,7 +11141,11 @@ function resolveEnvReferences(config, env, parentKey) {
11008
11141
  }
11009
11142
  function parseEnvPassword(content) {
11010
11143
  const match = content.match(/^DBCLI_PASSWORD=(.+)$/m);
11011
- return match?.[1] != null ? match[1].trim() : null;
11144
+ if (match?.[1] == null)
11145
+ return null;
11146
+ const raw = match[1].trim();
11147
+ const quoted = raw.startsWith('"') && raw.endsWith('"') || raw.startsWith("'") && raw.endsWith("'");
11148
+ return quoted && raw.length >= 2 ? raw.slice(1, -1) : raw;
11012
11149
  }
11013
11150
  var configModule = {
11014
11151
  async read(path, connectionName, options = {}) {
@@ -11187,7 +11324,7 @@ ${formatConfigValidationError(error, raw)}`);
11187
11324
  isDirectory = false;
11188
11325
  }
11189
11326
  if (isDirectory || path.endsWith(".dbcli") || path === storagePath && isDirectory) {
11190
- await mkdir3(storagePath, { recursive: true });
11327
+ await mkdir4(storagePath, { recursive: true });
11191
11328
  const hasEnvReferences = isEnvReference(config.connection.password);
11192
11329
  if (hasEnvReferences) {
11193
11330
  const configJson = JSON.stringify(config, null, 2);
@@ -11543,9 +11680,10 @@ var readConfig = (path, connectionName) => configModule.read(path, connectionNam
11543
11680
  export {
11544
11681
  writeV2Config,
11545
11682
  writeProjectBinding,
11546
- writeConnectionSecret,
11547
11683
  upsertConnection,
11548
11684
  setDefaultConnection,
11685
+ setConnectionPassword,
11686
+ resolvePasswordTarget,
11549
11687
  resolveConnection,
11550
11688
  resolveConfigStoragePath,
11551
11689
  removeConnection,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carllee1983/dbcli",
3
- "version": "1.56.0",
3
+ "version": "1.57.0",
4
4
  "description": "Database CLI for AI agents",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -310,6 +310,19 @@ dbcli init --rename staging:stg # rename
310
310
  dbcli init --remove stg # remove
311
311
  ```
312
312
 
313
+ Rotating one connection's password — nothing else in the config moves:
314
+
315
+ ```bash
316
+ dbcli password prod # masked prompt
317
+ rotate-secret | dbcli password prod --stdin # for scheduled rotation scripts
318
+ ```
319
+
320
+ The value goes to the env var the config actually references (a literal password
321
+ is converted to `{ "$env": ... }` on first use, and a connection with no
322
+ `envFile` gets one recorded so the reader loads it), is verified by connecting
323
+ before it is saved (`--skip-test` to opt out), and the env file is written
324
+ `0600` on POSIX.
325
+
313
326
  For a connection shared across projects, use the explicit root-level `--global` scope. It stores a v2 registry at `~/.config/dbcli/config.json`; it does not create or modify a project binding:
314
327
 
315
328
  ```bash
@@ -22,6 +22,7 @@ never the right move.
22
22
  **Commands** —
23
23
  [init](#init) ·
24
24
  [use](#use) ·
25
+ [password](#password) ·
25
26
  [list](#list) ·
26
27
  [schema](#schema) ·
27
28
  [query](#query) ·
@@ -196,6 +197,46 @@ dbcli list --use prod
196
197
 
197
198
  **Options:** `--list`, `--format <text|json>`, `--confirm-production <name>` (required when explicitly selecting a production connection as the default).
198
199
 
200
+ ### password
201
+
202
+ Change one connection's password without touching any other setting — built for
203
+ environments where credentials rotate on a schedule.
204
+
205
+ ```bash
206
+ dbcli password # Masked prompt, rotates the default connection
207
+ dbcli password prod # Masked prompt, rotates 'prod'
208
+ rotate-secret | dbcli password prod --stdin # Non-interactive, nothing lands in shell history
209
+ dbcli password prod --password "$NEW" --skip-test --format json
210
+ ```
211
+
212
+ Where the value lands is read from the config, never guessed: a connection whose
213
+ `password` is `{ "$env": "NAME" }` gets `NAME` rewritten in its `envFile`. A
214
+ connection that declares no `envFile` has one recorded (`.env.local`) as part of
215
+ the rotation — without it the reader would never load the file. A connection
216
+ still holding a literal password is converted to
217
+ `{ "$env": "DBCLI_<CONN>_PASSWORD" }` once, so later rotations only touch the env
218
+ file. Values are written quoted (`NAME="..."`), so leading and trailing
219
+ whitespace survives the round trip.
220
+
221
+ v1 configs rewrite `DBCLI_PASSWORD` in `.env.local`, matching the v1 reader. A v1
222
+ config whose password comes from some other environment variable is refused with
223
+ an explanation: v1 has no per-connection env file, so no file dbcli writes could
224
+ make that variable resolve — set it in the environment, or migrate to v2.
225
+
226
+ The new password is verified by connecting with it before anything is written,
227
+ so a bad rotation fails without leaving broken credentials behind. Pass
228
+ `--skip-test` when the database is unreachable from where the command runs. The
229
+ env file is written with `0600` permissions on POSIX systems (Windows has no
230
+ equivalent mode bit — the file inherits the directory's ACL), and the value is
231
+ never echoed or
232
+ logged.
233
+
234
+ **Options:** `[connection]`, `--stdin`, `--password <value>` (visible in shell
235
+ history and the process list — prefer `--stdin`), `--skip-test`,
236
+ `--format <text|json>`.
237
+
238
+ Blocked under `DBCLI_AGENT_MODE=1` like every other credential mutation.
239
+
199
240
  ### Agent configuration trust boundary
200
241
 
201
242
  When `DBCLI_AGENT_MODE=1`, configuration, permission, and credential mutations
@@ -310,6 +310,19 @@ dbcli init --rename staging:stg # rename
310
310
  dbcli init --remove stg # remove
311
311
  ```
312
312
 
313
+ Rotating one connection's password — nothing else in the config moves:
314
+
315
+ ```bash
316
+ dbcli password prod # masked prompt
317
+ rotate-secret | dbcli password prod --stdin # for scheduled rotation scripts
318
+ ```
319
+
320
+ The value goes to the env var the config actually references (a literal password
321
+ is converted to `{ "$env": ... }` on first use, and a connection with no
322
+ `envFile` gets one recorded so the reader loads it), is verified by connecting
323
+ before it is saved (`--skip-test` to opt out), and the env file is written
324
+ `0600` on POSIX.
325
+
313
326
  For a connection shared across projects, use the explicit root-level `--global` scope. It stores a v2 registry at `~/.config/dbcli/config.json`; it does not create or modify a project binding:
314
327
 
315
328
  ```bash
@@ -22,6 +22,7 @@ never the right move.
22
22
  **Commands** —
23
23
  [init](#init) ·
24
24
  [use](#use) ·
25
+ [password](#password) ·
25
26
  [list](#list) ·
26
27
  [schema](#schema) ·
27
28
  [query](#query) ·
@@ -196,6 +197,46 @@ dbcli list --use prod
196
197
 
197
198
  **Options:** `--list`, `--format <text|json>`, `--confirm-production <name>` (required when explicitly selecting a production connection as the default).
198
199
 
200
+ ### password
201
+
202
+ Change one connection's password without touching any other setting — built for
203
+ environments where credentials rotate on a schedule.
204
+
205
+ ```bash
206
+ dbcli password # Masked prompt, rotates the default connection
207
+ dbcli password prod # Masked prompt, rotates 'prod'
208
+ rotate-secret | dbcli password prod --stdin # Non-interactive, nothing lands in shell history
209
+ dbcli password prod --password "$NEW" --skip-test --format json
210
+ ```
211
+
212
+ Where the value lands is read from the config, never guessed: a connection whose
213
+ `password` is `{ "$env": "NAME" }` gets `NAME` rewritten in its `envFile`. A
214
+ connection that declares no `envFile` has one recorded (`.env.local`) as part of
215
+ the rotation — without it the reader would never load the file. A connection
216
+ still holding a literal password is converted to
217
+ `{ "$env": "DBCLI_<CONN>_PASSWORD" }` once, so later rotations only touch the env
218
+ file. Values are written quoted (`NAME="..."`), so leading and trailing
219
+ whitespace survives the round trip.
220
+
221
+ v1 configs rewrite `DBCLI_PASSWORD` in `.env.local`, matching the v1 reader. A v1
222
+ config whose password comes from some other environment variable is refused with
223
+ an explanation: v1 has no per-connection env file, so no file dbcli writes could
224
+ make that variable resolve — set it in the environment, or migrate to v2.
225
+
226
+ The new password is verified by connecting with it before anything is written,
227
+ so a bad rotation fails without leaving broken credentials behind. Pass
228
+ `--skip-test` when the database is unreachable from where the command runs. The
229
+ env file is written with `0600` permissions on POSIX systems (Windows has no
230
+ equivalent mode bit — the file inherits the directory's ACL), and the value is
231
+ never echoed or
232
+ logged.
233
+
234
+ **Options:** `[connection]`, `--stdin`, `--password <value>` (visible in shell
235
+ history and the process list — prefer `--stdin`), `--skip-test`,
236
+ `--format <text|json>`.
237
+
238
+ Blocked under `DBCLI_AGENT_MODE=1` like every other credential mutation.
239
+
199
240
  ### Agent configuration trust boundary
200
241
 
201
242
  When `DBCLI_AGENT_MODE=1`, configuration, permission, and credential mutations