Skip to content

Commit 9e0371c

Browse files
MpdreamzclaudeJoshMock
authored
feat: emit CLI structure as argh-schema JSON via elastic cli-schema (#359)
* feat: emit CLI structure as argh-schema JSON via `elastic cli-schema` Adds a `cli-schema` command and `npm run build:schema` script that walk the full Commander command tree and emit the CLI structure as a checked-in JSON artifact (docs/cli/schema.json) conforming to the argh CLI schema format. Key design choices: - `src/namespaces.ts` is the single source of truth for top-level namespace registration; adding a new namespace only requires one edit - `src/factory.ts` attaches `_commandConfig` (typed options + Zod schema + schemaArgs) to every leaf command so the emitter can recover full type/validation metadata without re-parsing Commander strings - ES API schemas are loaded eagerly only for `cli-schema`; all other invocations retain the existing lazy-load optimisation - Per-parameter fields emitted: `type`, `defaultValue`, `repeatable`, `separator`, `enumValues` (from Zod), `elementType`, `hidden` - Enum choices and array element types are extracted via the same `z.toJSONSchema()` call already used by `--help --json` Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * refactor: move cli-schema into NAMESPACES, make it data-driven Add cli-schema (and sanitize) to namespaces.ts alongside all other top-level namespaces. cli.ts now uses a single NAMESPACES loop for all commands rather than scattered if/else blocks, and skipConfig metadata drives both the preAction hook and early config load instead of hard-coded name checks. Breaks the potential circular import between cli-schema.ts and namespaces.ts by inverting the dependency: namespaces passes the namespace list as a parameter to registerCliSchemaCommand rather than cli-schema.ts importing NAMESPACES directly. Also fixes the unused OpaqueCommandHandle import (MegaLinter lint error) and the registerKbCommands arity regression introduced by the rebase onto main. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(schema): align cli-schema emitter with canonical argh-schema spec - Fix `schemaVersion` from string to integer (const `1`) - Rename parameter roles to spec-compliant lowercase (`flag`, `positional`) - Add `dryRun` role for the framework-injected `--dry-run` parameter - Add `integer` and `enum` type values; drop `object` (falls back to `string`) - Filter `elementType` to spec-allowed scalars (string/integer/number/boolean) - Add `CliIntent` (via `CommandIntent` from factory) and emit on leaf commands - Thread `requiresAuth` opt-out from `skipConfig` namespace metadata - Infer `destructive`/`idempotent`/`scope` from HTTP method for ES/KB API commands - Add `intent` to `CommandConfig`, `EsApiDefinition`, `KbApiDefinition` - New `src/cli-schema-intent.ts` with `inferIntentFromHttp` helper - Annotate helper commands (`bulk-ingest`, `scroll-search`, `msearch`, `watch`) - Add `environment` block (3 env vars, 4 config file paths) - Regenerate `docs/cli/schema.json`; validated against cli-schema.meta-schema.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: rename skipConfig→requiresContext, hoist namespace and global options - Rename NamespaceEntry.skipConfig → requiresContext (false = no context needed) to express intent rather than implementation side-effect - Add CliNamespace.options to emit namespace-level shared flags per spec §8 - Add hoistNamespaceOptions: lifts flags common to all leaf commands in a namespace up to namespace.options, stripping them from individual commands - Add promoteToGlobalOptions: further promotes flags universal across every namespace and root command into globalOptions - Result: --dry-run moves to globalOptions; --input-file to stack/cloud/docs namespace options; leaf commands only carry their own specific parameters - Regenerate docs/cli/schema.json (still validates against cli-schema.meta-schema.json) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: update global options table to include all current flags Add --command-profile, --output-fields, --output-template, and --dry-run which were missing from the README. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: make namespace shortcuts declarative in NAMESPACES registry - Add NamespaceShortcut interface (from, to, description) to namespaces.ts - Add shortcuts?: NamespaceShortcut[] to NamespaceEntry - Declare es/elasticsearch→stack.es and kb/kibana→stack.kb on the stack entry - cli.ts now drives argv rewriting and stub registration from NAMESPACES instead of hardcoded if/else blocks — adding a new shortcut requires only a namespaces.ts entry - cli-schema.ts emits shortcuts[] on the root schema object - Regenerate docs/cli/schema.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(schema): align shortcuts with spec — drop description field, fix stub grouping The merged cli-schema spec (PR #5) defines shortcut as {from, to} only — no description field. Also fix the stub registration to group by `to` path so elasticsearch/kibana become Commander aliases of es/kb rather than separate root commands. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Update src/cli-schema.ts * Update installation.md * Fix merge conflict --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Josh Mock <josh@joshmock.com> Co-authored-by: Josh Mock <joshua.mock@elastic.co>
1 parent f8ed66c commit 9e0371c

22 files changed

Lines changed: 55076 additions & 131 deletions

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,11 @@ elasticsearch:
288288
|---|---|
289289
| `--config-file <path>` | Path to a config file (default: `~/.elasticrc.yml`) |
290290
| `--use-context <name>` | Override the active context from the config file |
291+
| `--command-profile <name>` | Restrict available commands to a deployment profile (`full`, `serverless`) |
291292
| `--json` | Output results as JSON |
293+
| `--output-fields <list>` | Comma-separated list of fields to include in output (dot-notation supported) |
294+
| `--output-template <string>` | Mustache-like template for custom text output (e.g. `"{{id}}: {{name}}"`) |
295+
| `--dry-run` | Validate all inputs and exit without performing any action |
292296

293297
## Commands
294298

docs/cli/configuration.md

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# Configuration
2+
3+
The CLI looks for a config file in your home directory. The following file names are checked in order:
4+
5+
1. `.elasticrc`
6+
2. `.elasticrc.json`
7+
3. `.elasticrc.yaml`
8+
4. `.elasticrc.yml`
9+
10+
Place your config at `~/.elasticrc.yml` (recommended). To use a file in a different location, pass `--config-file <path>` or set `ELASTIC_CLI_CONFIG_FILE`. The flag takes precedence over the environment variable.
11+
12+
```yaml
13+
current_context: local
14+
15+
contexts:
16+
local:
17+
elasticsearch:
18+
url: http://localhost:9200
19+
auth:
20+
api_key: your-api-key-here
21+
kibana:
22+
url: http://localhost:5601
23+
auth:
24+
api_key: your-api-key-here
25+
staging:
26+
elasticsearch:
27+
url: https://my-cluster.es.us-east-1.aws.elastic.cloud
28+
auth:
29+
api_key: your-api-key-here
30+
cloud:
31+
url: https://api.elastic-cloud.com
32+
auth:
33+
api_key: your-cloud-api-key-here
34+
```
35+
36+
Multiple contexts are supported. Override `current_context` for a single command with `--use-context <name>`.
37+
38+
Each context can have any combination of service blocks (`elasticsearch`, `kibana`, `cloud`). Authentication supports `api_key` or `username` + `password`.
39+
40+
## Authoring the config from the CLI
41+
42+
Instead of hand-editing YAML, the `elastic config` command group creates and maintains contexts and stores secrets in the OS keychain when available (macOS Keychain, Linux libsecret, `pass`, Windows Credential Manager). The YAML then holds a resolver expression like `$(keychain:...)` rather than the raw secret.
43+
44+
```bash
45+
# Add a new context (API key goes to the keychain)
46+
elastic config context add local \
47+
--es-url http://localhost:9200 \
48+
--es-api-key your-api-key
49+
50+
# List contexts
51+
elastic config context list
52+
53+
# Switch the active context
54+
elastic config current-context set staging
55+
56+
# Patch an existing context
57+
elastic config context edit local --es-url http://localhost:9201
58+
59+
# Open the context as YAML in $EDITOR
60+
elastic config context edit local
61+
62+
# Remove a context (keychain entries are cleaned up)
63+
elastic config context remove old-lab
64+
```
65+
66+
If no OS keychain is available or you pass `--inline-secrets`, the secret is written inline and the file is `chmod 0600`. A warning is emitted when a loaded config has inline secrets at looser-than-0600 permissions.
67+
68+
## Credential-safe project creation
69+
70+
For agent and LLM workflows, `serverless projects create` and `reset-credentials` accept `--save-as <context>` to avoid leaking admin credentials through stdout:
71+
72+
```bash
73+
elastic cloud serverless es projects create --wait --save-as scratch \
74+
--name scratch-es --region-id aws-us-east-1
75+
76+
# stdout has endpoints + a `savedAs: scratch` marker, password is redacted.
77+
# The keychain now holds scratch:elasticsearch.auth.password etc.
78+
elastic --use-context scratch stack es indices list
79+
80+
# Rotate credentials; URL stays, only the password moves.
81+
elastic cloud serverless es projects reset-credentials --id <id> \
82+
--save-as scratch --force
83+
```
84+
85+
`--credentials-file <path>` writes a standalone YAML config fragment (0600) at `<path>` instead of mutating the main config. Either flag makes stdout safe to capture into an LLM transcript.
86+
87+
## External credentials
88+
89+
Any string value in the config file can use `$(resolver:params)` expressions to fetch secrets from external sources at runtime.
90+
91+
:::{warning}
92+
Review config files before using them if you didn't write them yourself. The `$(cmd:...)` and `$(file:...)` resolvers execute programs and read files on your behalf. This applies especially to CI/CD environments where a repo-checked-in config (e.g. via `ELASTIC_CLI_CONFIG_FILE`) can run arbitrary commands on the runner.
93+
:::
94+
95+
`file`
96+
: Reads the contents of a file (trimmed). Useful for Docker/Kubernetes secrets mounted at `/run/secrets/`.
97+
98+
```yaml
99+
auth:
100+
api_key: $(file:/run/secrets/elastic_api_key)
101+
```
102+
103+
`env`
104+
: Reads an environment variable.
105+
106+
```yaml
107+
auth:
108+
api_key: $(env:ELASTIC_API_KEY)
109+
```
110+
111+
`cmd`
112+
: Executes a shell command and uses its stdout (trimmed) as the value.
113+
114+
```yaml
115+
auth:
116+
api_key: $(cmd:pass show elastic/api-key)
117+
```
118+
119+
`keychain` *(macOS only)*
120+
: Reads a password from the macOS Keychain using `service/account` format.
121+
122+
```yaml
123+
auth:
124+
api_key: $(keychain:elastic-cli/api-key)
125+
```
126+
127+
To store a value: `security add-generic-password -s elastic-cli -a api-key -w`
128+
129+
`secret_service` *(Linux only)*
130+
: Reads a secret from GNOME Keyring or KWallet via `secret-tool`.
131+
132+
```yaml
133+
auth:
134+
api_key: $(secret_service:elastic-cli/api-key)
135+
```
136+
137+
To store a value: `secret-tool store --label='Elastic API Key' service elastic-cli account api-key`
138+
139+
`pass` *(cross-platform)*
140+
: Reads the first line from `pass show`. Works on Linux, macOS, and Windows (WSL).
141+
142+
```yaml
143+
auth:
144+
api_key: $(pass:elastic/api-key)
145+
```
146+
147+
To store a value: `pass insert elastic/api-key`
148+
149+
`credential_manager` *(Windows only)*
150+
: Reads a credential from Windows Credential Manager. Requires the `CredentialManager` PowerShell module.
151+
152+
```yaml
153+
auth:
154+
api_key: $(credential_manager:elastic-cli/api-key)
155+
```
156+
157+
To store a value: `New-StoredCredential -Target elastic-cli/api-key -UserName _ -Password <key>`
158+
159+
Expressions can appear in any string field, including URLs:
160+
161+
```yaml
162+
elasticsearch:
163+
url: https://$(env:ES_HOST):9200
164+
auth:
165+
api_key: $(keychain:elastic-cli/api-key)
166+
```

docs/cli/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Interact with the Elastic Stack and Elastic Cloud from the command line.
2+
3+
Configure the CLI with `elastic config context add` to connect to your Elasticsearch, Kibana, and Elastic Cloud endpoints. See [Installation](./installation.md) and [Configuration](./configuration.md) to get started.

docs/cli/installation.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Installation
2+
3+
Install globally from `npm` so the elastic binary is available on your `PATH`:
4+
5+
```bash
6+
npm install -g @elastic/cli
7+
elastic --help
8+
```
9+
10+
If you don't want a global install, you can run a one-off invocation with npx, which downloads and runs the CLI without persisting it:
11+
12+
```bash
13+
npx -y @elastic/cli --help
14+
```

0 commit comments

Comments
 (0)