Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 29 additions & 87 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,9 @@ that currently drives two independent products:
- **Workflow Studio** (wires a repository + branch to a deploy pipeline and
publishes it) — `eds wf app|run|job *`.

These are separate products with **independent credentials**, namespaced
accordingly. Repo takes a static `--repo-api-key`/`EDS_REPO_API_KEY` sent
as `X-API-KEY`. Workflow Studio takes a `--wf-key-id`/`--wf-secret` pair
(`EDS_WF_KEY_ID`/`EDS_WF_SECRET`) that the CLI exchanges for a short-lived
Bearer access token via the cloud.ru IAM service (`internal/iam`) the
first time it's needed, caching the token + its expiry in the config file
and re-exchanging automatically once it expires — callers never handle a
raw Workflow Studio token themselves. `--project`/`EDS_PROJECT_ID` and
`--iam-url`/`EDS_IAM_URL` are the only platform-level (unprefixed)
settings, shared across products. Having one product's credential
configured does not imply the other is.
Both products are authenticated with the same API key (`--api-key`/`EDS_API_KEY`),
sent as `X-API-KEY`. `--project`/`EDS_PROJECT_ID` is the only other platform-level
(shared, unprefixed) setting.

The CLI is explicitly designed to be **agent-friendly**: every command has
stable `--json` output, config comes from env vars, and `skill/SKILL.md`
Expand Down Expand Up @@ -69,42 +61,25 @@ genuinely internal and not tied to where the repo lives.

Cobra-based CLI, one command per file under `cmd/`, thin API clients under
`internal/repoapi/` (Repo product) and `internal/workflowapi/` (Workflow
Studio), plus a small `internal/iam/` client used only to mint Workflow
Studio's Bearer token.
Studio).

- `main.go` — entry point, wires `main.version` (ldflags) into `cmd.SetVersion`.
- `cmd/root.go` — builds the root `eds` command, registers global persistent
flags. Product-scoped: `--repo-api-url`, `--repo-api-key`, `--repo-git-host`
(Repo product); `--wf-api-url`, `--wf-key-id`, `--wf-secret` (Workflow
Studio). Platform-level (shared, unprefixed): `--project`, `--iam-url`.
Plus `--json`, `--quiet`, and the TEMPORARY `--repo-use-wf-auth` (see below).
flags. Product-scoped: `--repo-api-url`, `--repo-git-host`
(Repo product); `--wf-api-url` (Workflow Studio). Platform-level (shared,
unprefixed): `--project`, `--api-key`. Plus `--json`, `--quiet`.
- `cmd/helpers.go` — `resolveContext(cmd)` is the entry point every subcommand
calls first. It loads config, layers flag overrides on top (flags > env >
file > defaults), and builds a `runtimeContext{Cfg, API, WorkflowAPI,
Printer, Quiet}`. `requireAPIKey()` guards Repo-product commands.
`ensureWorkflowAuth(ctx)` guards Workflow Studio commands: it reuses the
cached access token in `Cfg` if still valid *and* was minted for the
currently-active `WorkflowKeyID` (see `WorkflowAccessTokenKeyID` below),
otherwise calls `internal/iam.FetchToken` to exchange `WorkflowKeyID`/
`WorkflowSecret` for a fresh one, pushes it into `WorkflowAPI` via
`SetToken`, and persists the new token + expiry + key id back to the
config file (reloaded fresh from disk first, so it doesn't also persist
unrelated one-off flag overrides for this run). `resolveContext` also
implements the temporary `--repo-use-wf-auth`/`EDS_REPO_USE_WF_AUTH`
escape hatch: when set, it calls `ensureWorkflowAuth` unconditionally and
hands the resulting Bearer token to `ctx.API.SetBearerToken`, so Repo
commands authenticate the same way Workflow Studio does. This exists
because some cloud.ru environments don't (yet) accept `X-API-KEY` for the
Repo product; remove `--repo-use-wf-auth`, `SetBearerToken`, and
`UsesBearerAuth` once that's fixed server-side everywhere.
`ensureWorkflowAuth(ctx)` guards Workflow Studio commands: it validates
that `APIKey` is set (the same key is used for both products). Both helpers
return the same friendly error when the key is missing.
- `cmd/login.go`, `cmd/config.go`, `cmd/version.go`, `cmd/repo.go` — one
subcommand tree each. `repo.go` holds `eds repo list|create|show|delete|clone`
plus `resolveRepoID`, which lets users pass either a UUID or a repo name
(falls back to listing + case-insensitive name match when the arg doesn't
look like a UUID). `login.go` accepts the Repo-product and Workflow Studio
credential pairs independently (either or both); setting a new
`--wf-key-id`/`--wf-secret` clears any cached access token so it isn't
reused across a credential change.
look like a UUID). `login.go` accepts `--api-key` plus optional product URLs.
- `cmd/wf.go` — the `eds wf` parent command; just groups `newAppCmd()`,
`newRunCmd()`, `newJobCmd()` as children. Doesn't hold any logic itself.
- `cmd/app.go`, `cmd/run.go`, `cmd/job.go` — Workflow Studio subcommand
Expand Down Expand Up @@ -134,20 +109,8 @@ Studio's Bearer token.
`$XDG_CONFIG_HOME/eds/config.json`) -> `EDS_*` env vars -> CLI flags
(flags applied later, in `cmd/helpers.go`). Config file is written with
`0600` permissions and is a single platform-level file shared by both
products (not split per-product), since project id/IAM are shared. Also
holds the Workflow Studio credential pair (`WorkflowKeyID`/`WorkflowSecret`)
and the CLI-managed token cache (`WorkflowAccessToken`/
`WorkflowTokenExpiresAt`/`WorkflowAccessTokenKeyID`) — these three have no
flag/env equivalent since they're never meant to be set directly.
`WorkflowAccessTokenKeyID` records which `WorkflowKeyID` the cached token
was minted for; `ensureWorkflowAuth` compares it against the currently
active `WorkflowKeyID` before trusting the cache. Without this check,
switching Workflow Studio credentials via flag/env (rather than through
`login`, which explicitly clears the cache) would silently keep reusing a
token minted for the *old* credentials until it expired -- this bit us
switching from a dev to a prod `wf-key-id` while the disk cache still held
a live dev-minted token. Note: the internal `Config`
struct field names and JSON keys (`api_url`, `workflow_key_id`, etc.) keep
products (not split per-product), since project id is shared. Note: the
internal `Config` struct field names and JSON keys (`api_url`, etc.) keep
their pre-rename spelling — only the user-facing CLI flags/env vars/command
names follow the `eds`/`repo`/`wf` product split; this is a deliberate
minimal-diff choice, not an oversight.
Expand All @@ -157,36 +120,28 @@ Studio's Bearer token.
output modes stay in sync from one call site. Also has `HumanTime` /
`HumanSize` formatters.
- `internal/repoapi/client.go` — generic authenticated HTTP client (`Do`),
sends `X-API-KEY` by default, decodes JSON, maps non-2xx responses to
sends `X-API-KEY`, decodes JSON, maps non-2xx responses to
`*APIError` (tries `error`, `message`, and per-field `errors` response
shapes; also captures a server-assigned request id from common headers
like `X-Request-Id` via `requestIDFromHeader` and includes it in
`APIError.Error()` when present -- the single most useful thing to grab
when reporting a bug to the API team, since bodies are sometimes empty
even on a 500). Also has `SetBearerToken`/`UsesBearerAuth` -- TEMPORARY,
backs `--repo-use-wf-auth` (see `cmd/helpers.go` above); remove together.
even on a 500).
- `internal/repoapi/repositories.go` — repository-specific request/response
types and the four API calls (`ListRepositories`, `GetRepository`,
`CreateRepository`, `DeleteRepository`), all scoped under
`/project/{projectID}/repository[ies]`. `ListRepositories` omits the
`search` query param entirely when empty — the API treats an empty string
as a literal (matches nothing) rather than a wildcard.
- `internal/workflowapi/client.go` — the Workflow Studio counterpart to
`repoapi/client.go`: same `Do`/`APIError`/request-id shape, but sends
`Authorization: Bearer <token>` instead of `X-API-KEY`, has no built-in
request timeout (bounded by the caller's context instead, since `logs`
can stay open for a running job), and adds `Stream` for reading an SSE
response body line-by-line plus `SetToken` to swap the token after an
IAM refresh.
`repoapi/client.go`: same `Do`/`APIError`/request-id shape, sends
`X-API-KEY` (the same key as Repo), has no built-in request timeout
(bounded by the caller's context instead, since `logs` can stay open for
a running job), and adds `Stream` for reading an SSE response body
line-by-line.
- `internal/workflowapi/application.go`, `run.go`, `job.go` —
application/deployment, run/stage, and job request/response types plus
the API calls, all scoped under `/project/{projectID}/...`.
- `internal/iam/client.go` — `FetchToken(ctx, iamURL, keyID, secret)`
exchanges Workflow Studio's key id/secret for a Bearer `access_token`
(Keycloak-shaped response: `access_token`, `expires_in`, etc.) against
`https://iam.api.cloud.ru/api/v1/auth/token`. This is the **only**
consumer of the IAM endpoint — everything else in the CLI talks to
`repoapi`/`workflowapi`, never IAM directly.
- `openapi-user.yaml` — OpenAPI (Swagger 2.0) spec for the Workflow Studio
user API; the source of truth for request/response shapes when extending
`internal/workflowapi`.
Expand All @@ -208,35 +163,22 @@ Studio's Bearer token.

## Conventions specific to this codebase

- Every new Repo-product subcommand should start with
`ctx, err := resolveContext(cmd)` and, if it hits the API,
`ctx.requireAPIKey()`. Every new Workflow Studio subcommand
(under `eds wf`: `app`/`run`/`job`) should call
`ctx.ensureWorkflowAuth(cmd.Context())` instead — it both validates
credentials and refreshes the cached token.
- Never add a way to set `WorkflowAccessToken`/`WorkflowTokenExpiresAt`/
`WorkflowAccessTokenKeyID` directly (no flag, no env var); they are
populated only by `ensureWorkflowAuth`. If Workflow Studio's auth model
changes, change `internal/iam` and `ensureWorkflowAuth`, not the cache
fields themselves.
- `--repo-use-wf-auth`/`EDS_REPO_USE_WF_AUTH` is a deliberately TEMPORARY
escape hatch (Repo product auth via the Workflow Studio Bearer token
instead of `X-API-KEY`) for environments where Repo's own API key auth
isn't accepted yet. If you add another workaround flag of this shape,
label it TEMPORARY in the flag help text and in code comments the same
way, so it's easy to grep for and remove later.
- Every new subcommand should start with `ctx, err := resolveContext(cmd)`.
If it hits a Repo-product API, call `ctx.requireAPIKey()`. If it hits a
Workflow Studio API, call `ctx.ensureWorkflowAuth(cmd.Context())` — both
validate that `EDS_API_KEY` is set.
- Every subcommand that returns structured data should support both table
(default on TTY) and `--json` output via `ctx.Printer`, not ad-hoc
`fmt.Println`.
- `eds repo` commands (and `eds wf app create --repository`) accept either a
repository UUID or a name (`resolveRepoID` / `looksLikeUUID`); don't
require callers to look up IDs first.
- New flags/env vars must follow the existing product-scoping convention:
Repo-product settings get a `repo-`/`REPO_` infix (`--repo-api-key`,
`EDS_REPO_API_KEY`), Workflow Studio settings get a `wf-`/`WF_` infix
(`--wf-key-id`, `EDS_WF_KEY_ID`), and only genuinely cross-product
settings (project id, IAM) stay unprefixed. Don't add a third product's
settings without picking an analogous short infix.
Repo-product settings get a `repo-`/`REPO_` infix (`--repo-api-url`,
`EDS_REPO_API_URL`), Workflow Studio settings get a `wf-`/`WF_` infix
(`--wf-api-url`, `EDS_WF_API_URL`), and only genuinely cross-product
settings (project id and the API key) stay unprefixed. Don't add a third
product's settings without picking an analogous short infix.
- Errors returned from `RunE` are printed as a single `Error: <msg>` line to
stderr by `cmd.Execute()` (root sets `SilenceUsage`/`SilenceErrors`) — keep
error messages one line and actionable (see `requireAPIKey`'s message as
Expand Down
73 changes: 24 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,17 @@ developer tools products. It currently covers two products:
- **Workflow Studio** (`eds wf`) — wires a repository + branch to a deploy
pipeline and publishes it.

Repo and Workflow Studio are independent products with independent
credentials; `eds` is just the platform CLI they're both driven through.
Every command is designed to be safely driven by automation and AI
agents: stable `--json` output, environment variables for secrets.
Repo and Workflow Studio are independent products, but both are authenticated
with the same API key (`EDS_API_KEY`) via `X-API-KEY`. `eds` is just the
platform CLI they're both driven through. Every command is designed to be safely
driven by automation and AI agents: stable `--json` output, environment variables
for secrets.

- Single static binary (Go, no runtime dependencies).
- Reads Repo's API key from `EDS_REPO_API_KEY`, Workflow Studio's key
id/secret from `EDS_WF_KEY_ID`/`EDS_WF_SECRET`, or from
`~/.config/eds/config.json`.
- Reads the API key from `EDS_API_KEY` or from `~/.config/eds/config.json`.
- Outputs JSON when piped, pretty tables on a TTY (`--json` to force).
- Repo is authenticated with `X-API-KEY` and uses the local `git` CLI for
clone. Workflow Studio is authenticated with a Bearer token that `eds`
mints on your behalf (see "Workflow Studio auth" below).
- Repo uses the local `git` CLI for clone. Both products authenticate with
`X-API-KEY`.

## Installation

Expand Down Expand Up @@ -65,17 +63,16 @@ The CLI looks for configuration in this order (later wins):

1. Built-in defaults: Repo API URL `https://devtools.api.cloud.ru/repo/api/v1`,
git host `https://repo.cloud.ru/`, Workflow Studio API URL
`https://pipeline.cloud.ru/public-api/v1`, IAM URL
`https://iam.api.cloud.ru/api/v1/auth/token`.
`https://pipeline.cloud.ru/public-api/v1`.
2. File `~/.config/eds/config.json` (overridable via `EDS_CONFIG` or
`XDG_CONFIG_HOME`) — a single platform-level file shared by both products.
3. Environment variables (see table below).
4. Per-command flags.

Env vars and flags are namespaced by product — `EDS_REPO_*`/`--repo-*` for
Repo, `EDS_WF_*`/`--wf-*` for Workflow Studio — except for the truly
platform-level settings (`EDS_PROJECT_ID`/`--project`, `EDS_IAM_URL`/`--iam-url`),
which are shared.
Env vars and flags are namespaced by product for URLs — `EDS_REPO_*`/`--repo-*`
for Repo, `EDS_WF_*`/`--wf-*` for Workflow Studio — except for `--api-key`/
`EDS_API_KEY` and `--project`/`EDS_PROJECT_ID`, which are shared platform-level
settings.

Example config file (`~/.config/eds/config.json`):

Expand All @@ -85,12 +82,7 @@ Example config file (`~/.config/eds/config.json`):
"project_id": "3232b2d0-1063-41e6-b2fa-13df767f4a0a",
"api_key": "...",
"git_host": "https://repo.cloud.ru/",
"workflow_api_url": "https://pipeline.cloud.ru/public-api/v1",
"workflow_key_id": "...",
"workflow_secret": "...",
"iam_url": "https://iam.api.cloud.ru/api/v1/auth/token",
"workflow_access_token": "... (cached, managed by the CLI)",
"workflow_token_expires_at": "... (cached, managed by the CLI)"
"workflow_api_url": "https://pipeline.cloud.ru/public-api/v1"
}
```

Expand All @@ -104,16 +96,9 @@ Override with `--repo-api-url` or `EDS_REPO_API_URL`.

### Workflow Studio auth

Workflow Studio has its own base URL (`--wf-api-url` / `EDS_WF_API_URL`) and
its own credential pair (`--wf-key-id`/`--wf-secret` or
`EDS_WF_KEY_ID`/`EDS_WF_SECRET`) — it is **not** authenticated with
`--repo-api-key`/`EDS_REPO_API_KEY`. The key id/secret are exchanged for a
short-lived Bearer access token via the cloud.ru IAM service
(`POST /api/v1/auth/token` at `--iam-url` / `EDS_IAM_URL`, default
`https://iam.api.cloud.ru/api/v1/auth/token`). The CLI does this
automatically before the first `eds wf ...` call and caches the resulting
token (and its expiry) in the config file, re-exchanging it once it
expires — you never need to call the IAM endpoint yourself.
Workflow Studio uses the same API key as Repo (`--api-key` / `EDS_API_KEY`),
sent as `X-API-KEY` on every request. There is no separate credential pair or
token exchange — the same key works for both `eds repo *` and `eds wf *`.

## Commands

Expand All @@ -134,8 +119,8 @@ eds wf app show <id> show application details
eds wf app update <id> --branch B [--name N] update name/branch
eds wf app delete <id> [--force] delete an application
eds wf app deploy <id> run the pipeline and publish
eds wf app deployments <id> list publish history
eds wf app status <id> run/stage/job status + live URL
eds wf app deployments <id> list publish history
eds wf app status <id> run status + live URL

eds wf run show <id> show a run's status, stages and jobs
eds wf run list [--pipeline-id ID] list runs
Expand All @@ -151,22 +136,17 @@ eds wf job stop <id> stop a running job
### Login

```bash
eds login --repo-api-key "$EDS_REPO_API_KEY" --project <project-id>
eds login --api-key "$EDS_API_KEY" --project <project-id>
eds login --repo-api-url https://devtools.dev.api.internal.cloud.ru/repo/api/v1 \
--repo-api-key "$EDS_REPO_API_KEY" --project <project-id>

# Workflow Studio uses a separate credential pair, exchanged for a Bearer
# token via cloud.ru IAM on first use (cached automatically afterwards)
eds login --wf-key-id "$WF_KEY_ID" --wf-secret "$WF_SECRET" \
--project <project-id>
--api-key "$EDS_API_KEY" --project <project-id>
```

### For an AI agent

The CLI is designed to be safely scripted. Recommended pattern:

```bash
export EDS_REPO_API_KEY="..."
export EDS_API_KEY="..."
export EDS_PROJECT_ID="..."

# List repos as JSON, pipe into jq
Expand Down Expand Up @@ -201,8 +181,7 @@ produces a live URL. This is the CLI's main "vibe-coded a site, now ship it"
path — `eds repo` gets your code hosted, `eds wf app` publishes it.

```bash
export EDS_WF_KEY_ID="..." # separate credential pair from EDS_REPO_API_KEY
export EDS_WF_SECRET="..." # exchanged for a Bearer token via cloud.ru IAM automatically
export EDS_API_KEY="..."
export EDS_PROJECT_ID="..."

# 1. Create the repo and push code (see "File upload / push" below)
Expand Down Expand Up @@ -307,7 +286,6 @@ internal/
output/ # JSON / table formatting
repoapi/ # thin HTTP client for the Repo product API
workflowapi/ # thin HTTP client for the Workflow Studio product API
iam/ # exchanges wf-key-id/secret for a Bearer token
scripts/
install.sh # one-liner installer (GitHub Releases by default)
skill/
Expand All @@ -320,13 +298,10 @@ Makefile # build, build-all, release, upload, …
| Variable | Description |
| ----------------- | ------------------------------------------------------------ |
| `EDS_PROJECT_ID` | Default project ID (shared across products) |
| `EDS_IAM_URL` | cloud.ru IAM token endpoint (shared, overrides config) |
| `EDS_REPO_API_URL` | Repo product API base URL (overrides config) |
| `EDS_REPO_API_KEY` | Repo product X-API-KEY value |
| `EDS_API_KEY` | API key used for both products (X-API-KEY) |
| `EDS_REPO_GIT_HOST` | Repo product git smart-HTTP host |
| `EDS_WF_API_URL` | Workflow Studio product API base URL (overrides config) |
| `EDS_WF_KEY_ID` | Workflow Studio key id (separate from Repo's API key) |
| `EDS_WF_SECRET` | Workflow Studio secret (paired with key id) |
| `EDS_CONFIG` | Path to config file (overrides default) |
| `XDG_CONFIG_HOME` | Respected when locating the config file |
| `AWS_ENDPOINT_URL` | S3-compatible endpoint for `make upload` (optional mirror) |
Expand Down
Loading
Loading