@@ -430,12 +430,122 @@ credentials:
430430| -------------------------- | ------------------------------------------------------------- |
431431| `sources` | Map of service identifier to credential source. |
432432| `sources.<id>.env` | Environment variables to read on the host, in priority order. |
433- | `sources.<id>.file.path` | Path on host. `~` expands to home. |
434- | `sources.<id>.file.parser` | How to extract the value (for example, `"json:apiKey"`). |
433+ | `sources.<id>.file.path` | Path on host. `~` expands to home directory. |
434+ | `sources.<id>.file.parser` | How to extract the credential value from the file. |
435435| `sources.<id>.priority` | `env-first` (default) or `file-first`. |
436436
437437Service identifiers link credentials to [network rules](#network).
438438
439+ # ### file.parser
440+
441+ ` file.parser` controls how the proxy extracts a credential value from the file at
442+ `file.path`. Three forms are supported :
443+
444+ | Parser value | Behavior |
445+ | ----------------- | ------------------------------------------------------------------------------------ |
446+ | omitted or empty | Reads the entire file as the credential. Leading and trailing whitespace is trimmed. |
447+ | `json:<dot.path>` | Parses the file as JSON and extracts the value at the given dot-separated path. |
448+ | anything else | Rejected with `unsupported parser : <value>`. |
449+
450+ ` json:` is the only supported structured-file format.
451+
452+ **JSON path rules**
453+
454+ - Path segments are separated by `.` — for example, `json:credentials.github.token`.
455+ - Only JSON objects can be navigated. Arrays are not supported; there is no `[0]`-style indexing.
456+ - Keys that contain a literal `.` cannot be referenced — the dot is always treated as a separator.
457+ - The value at the resolved path must be a string, number, or boolean. Numbers and booleans are
458+ converted to strings. Objects, arrays, and null are rejected with
459+ ` field '<path>' is not a string value` .
460+
461+ **Priority and missing files**
462+
463+ When a source has both `env` and `file`, `priority` controls which is tried first. If the
464+ preferred source is unavailable (environment variable unset, or file missing or inaccessible),
465+ the proxy falls back to the other source automatically. A missing file is not an error on its
466+ own — the source is silently skipped.
467+
468+ **Examples**
469+
470+ Plain-text token file — no parser needed :
471+
472+ ` ` ` yaml
473+ credentials:
474+ sources:
475+ openai:
476+ file:
477+ path: "~/.openai/token"
478+ ` ` `
479+
480+ Top-level JSON field :
481+
482+ ` ` ` yaml
483+ credentials:
484+ sources:
485+ anthropic:
486+ file:
487+ path: "~/.claude/settings.json"
488+ parser: "json:primaryApiKey"
489+ ` ` `
490+
491+ Given `~/.claude/settings.json` :
492+
493+ ` ` ` json
494+ { "primaryApiKey": "sk-ant-...", "secondaryApiKey": "sk-ant-..." }
495+ ` ` `
496+
497+ The proxy resolves the credential to the value of `primaryApiKey`.
498+
499+ Nested JSON field :
500+
501+ ` ` ` yaml
502+ credentials:
503+ sources:
504+ github:
505+ file:
506+ path: "~/.config/myapp/creds.json"
507+ parser: "json:credentials.github.token"
508+ ` ` `
509+
510+ Given `~/.config/myapp/creds.json` :
511+
512+ ` ` ` json
513+ {
514+ "credentials": {
515+ "github": { "token": "ghp_xyz", "expires": "2026-12-31" }
516+ }
517+ }
518+ ` ` `
519+
520+ The proxy resolves the credential to `ghp_xyz`.
521+
522+ File-first with env fallback :
523+
524+ ` ` ` yaml
525+ credentials:
526+ sources:
527+ anthropic:
528+ env:
529+ - ANTHROPIC_API_KEY
530+ file:
531+ path: "~/.claude/settings.json"
532+ parser: "json:primaryApiKey"
533+ priority: file-first
534+ ` ` `
535+
536+ Tries the JSON file first; falls back to the environment variable if the file is
537+ missing or the field is absent.
538+
539+ **Common errors**
540+
541+ | Error message | Cause |
542+ | --------------------------------------------- | ------------------------------------------------------------------- |
543+ | `field 'X' not found in JSON` | The path doesn't exist in the file. |
544+ | `cannot navigate to field 'X' : not an object` | A path segment hit a string, array, or scalar instead of an object. |
545+ | `field 'X' is not a string value` | The resolved value is an object, array, or null. |
546+ | `failed to parse JSON : ...` | The file is not valid JSON. |
547+ | `unsupported parser : <value>` | The parser string doesn't start with `json:` and isn't empty. |
548+
439549# ## Network
440550
441551` ` ` yaml
0 commit comments