This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
eds (Evolution DevServices CLI, repo: git@github.com:cloud-ru/evolution-devservices-cli.git)
is a Go CLI client for cloud.ru "developer tools" — a platform CLI
that currently drives two independent products:
- the Repo product (git repositories in the cloud, at
devtools.api.cloud.ru/repo) —eds repo *. - Workflow Studio (wires a repository + branch to a deploy pipeline and
publishes it) —
eds wf app|run|job *.
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 outputs
stable JSON by default, config comes from env vars, and skills/eds/SKILL.md
documents the CLI's contract for AI agents driving it. The primary
agent-facing scenario is ship a vibe-coded app: eds repo create +
git push gets code hosted, eds wf app create publishes it (auto-deploy
on create), and eds wf app show (or the lower-level eds wf run/
eds wf job) tracks the rollout.
make build # build for current platform -> ./bin/eds
make build-all # cross-compile darwin/linux x amd64/arm64 -> ./dist/
make test # go test ./...
make lint
make tidy # go mod tidy
make clean # remove ./bin and ./distThere are currently no _test.go files in the repo, so make test is a no-op
until tests are added. make all runs lint test build in sequence.
Building directly without make:
go build -o bin/eds ./cmd/eds
go run ./cmd/eds <command>Version is injected at build time via -ldflags "-X main.version=..." (see
Makefile's LDFLAGS); running via go run yields version dev. The Go
module path is github.com/cloud-ru/evolution-devservices-cli, matching the
repo's actual GitHub location — unlike the earlier evo rebrand (which kept
the pre-rename module path as a cosmetic-only, minimal-diff change), this
rename moved the repo itself to a new GitHub org/name, so go install github.com/cloud-ru/evolution-devservices-cli/cmd/eds@latest needs the module path
to match. The internal Config struct field names and JSON keys still keep
their older spelling (see the internal/config/config.go entry below) —
that part of the prior minimal-diff choice still applies, since those are
genuinely internal and not tied to where the repo lives.
Cobra-based CLI, one command per file under cmd/, thin API client under
internal/repoapi/ (Repo product) and generated OpenAPI client under
internal/workflow_client/ (Workflow Studio).
cmd/eds/main.go— entry point, wiresmain.version(ldflags) intocmd.SetVersion.cmd/root.go— builds the rootedscommand, registers global persistent flags. Product-scoped:--repo-api-url,--repo-git-host(Repo product);--wf-api-url(Workflow Studio). Platform-level (shared, unprefixed):--project,--api-key. Plus--yaml,--quiet(--jsonis a backward-compatible no-op, since JSON is the default). the API client and printer.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 aruntimeContext{Cfg, API, Printer, Quiet, ProjectID, WorkflowClient}.requireAPIKey()guards Repo-product commands.ensureWorkflowAuth(ctx)guards Workflow Studio commands: it validates thatAPIKeyis 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.goholdseds repo list|create|show|delete|cloneplusresolveRepoID, 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.goaccepts--api-keyplus optional product URLs.cmd/wf.go— theeds wfparent command; just groupsnewAppCmd(),newRunCmd(),newJobCmd()as children. Doesn't hold any logic itself.cmd/app.go,cmd/run.go,cmd/job.go— Workflow Studio subcommand trees, nested undereds wf.app.goholdseds wf app create|list|show| update|delete|deploy|deployments(an application wires a repo+branch to a deploy pipeline; a deployment runs that pipeline and publishes it).app create --repositoryreusesresolveRepoIDfromrepo.goto accept either a Repo-product UUID or name.run.go/job.goare the lower-level primitives behind it. Note (observed empirically, not documented in the spec):application.statusandapplication.run.statusare separate lifecycles. Creating an application auto-triggers its first run; even afterrun.statusreachesdone,application.statuscan sit atpublishingfor a while longer (the underlying container/route is starting async) before settling onrunning(live) orerror. Pollapplication.status, not justrun.status, to know when a deployment is actually live.eds wf app deletehas also been observed returning a 500 while still mutating server-side state (statusflips througherror/deletedacross repeated calls) -- treat it as unreliable/best-effort for now, not a guaranteed synchronous delete.internal/config/config.go—Configstruct +Load()/Save(). Precedence is defaults ->~/.config/eds/config.json(or$EDS_CONFIG/$XDG_CONFIG_HOME/eds/config.json) ->EDS_*env vars -> CLI flags (flags applied later, incmd/helpers.go). Config file is written with0600permissions and is a single platform-level file shared by both products (not split per-product), since project id is shared. Note: the internalConfigstruct field names and JSON keys (api_url, etc.) keep their pre-rename spelling — only the user-facing CLI flags/env vars/command names follow theeds/repo/wfproduct split; this is a deliberate minimal-diff choice, not an oversight.internal/output/output.go—Printeroutputs JSON by default or YAML when--yamlis passed.--quietsuppresses all printer output. All commands render throughPrinter.Print(v)so output stays consistent. The--jsonflag is retained as a backward-compatible no-op.internal/repoapi/client.go— generic authenticated HTTP client (Do), sendsX-API-KEY, decodes JSON, maps non-2xx responses to*APIError(trieserror,message, and per-fielderrorsresponse shapes; also captures a server-assigned request id from common headers likeX-Request-IdviarequestIDFromHeaderand includes it inAPIError.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).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].ListRepositoriesomits thesearchquery param entirely when empty — the API treats an empty string as a literal (matches nothing) rather than a wildcard.internal/workflow_client/— generated OpenAPI client for the Workflow Studio user API (fromopenapi-public.yaml). Covers services, pipelines, and workflows APIs.cmd/calls into this instead of the older hand-writteninternal/workflowapi.openapi-public.yaml— OpenAPI (Swagger 2.0) spec for the Workflow Studio user API; the source of truth for request/response shapes and for regeneratinginternal/workflow_client.swagger/swagger.yaml— OpenAPI spec for the upstream Repo API; the source of truth for request/response shapes when extendinginternal/repoapi.swagger/swagger.ru.yamlis a Russian-translated copy (allsummary/descriptiontext values only — schema keys, types,$refs untouched) for review purposes; keep it in sync by hand whenswagger.yamlis re-synced from upstream.scripts/install.sh— self-installer (curl | bash), not part of the Go build. Downloadseds-<os>-<arch>assets from the latest GitHub Release ofcloud-ru/evolution-devservices-cliby default and installs aseds;EDS_CLI_BASE_URL/--base-urlpoints it at a custom mirror (e.g. the optional S3 upload frommake upload) instead.skills/eds/SKILL.md— Agent Skills description of the CLI's command surface, config precedence and error-handling contract for AI agents. Keep this in sync withcmd/when commands/flags change, since agents rely on it verbatim.
- Every new subcommand should start with
ctx, err := resolveContext(cmd). If it hits a Repo-product API, callctx.requireAPIKey(). If it hits a Workflow Studio API, callctx.ensureWorkflowAuth(cmd.Context())— both validate thatEDS_API_KEYis set. - Repo subcommands and Workflow Studio subcommands both output JSON by
default via
ctx.Printer. Use--yamlfor YAML output.--quietsuppresses all printer output. eds repocommands (andeds 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-url,EDS_REPO_API_URL), Workflow Studio settings get awf-/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
RunEare printed as a singleError: <msg>line to stderr bycmd.Execute()(root setsSilenceUsage/SilenceErrors) — keep error messages one line and actionable (seerequireAPIKey's message as the template). - Target platforms are Linux + macOS + Windows (
DEFAULT_OSES := darwin linux windowsin the Makefile). - If you ever scaffold a
Dockerfilefor a Workflow Studio deploy test (fixtures, examples, docs), it must run as non-root with a read-only-ish root filesystem -- that's what the Container Apps runtime enforces. Plainnginx:alpinecrash-loops there (mkdir /var/cache/nginx/client_temp: Permission denied); usenginxinc/nginx-unprivileged:alpineandEXPOSE 8080instead. Confirmed empirically end-to-end against prod. - When the CLI's user-facing command surface changes (new command, new flag,
changed defaults), update
README.md's Commands section andskills/eds/SKILL.mdin the same change — both are hand-maintained docs that will drift silently otherwise.