SAP ADT CLI

SkillDev tools

"Read and write ABAP source code and metadata from SAP systems via the ADT REST API.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the SAP ADT CLI skill

What this skill tells your AI

The instructions your AI receives, as published by shrek-abaper/sap-engineering-skill in skills/sap-adt-cli/SKILL.md and read by ahel’s review.

CLI: scripts/sap_adt_cli.py in this skill directory. First run auto-installs click/requests/urllib3. Always run status first to check the profile and switches.

SKILL_DIR="$(dirname "$(realpath "${BASH_SOURCE[0]:-$0}")")"
SAP_CLI="$SKILL_DIR/scripts/sap_adt_cli.py"
python3 "$SAP_CLI" status
python3 "$SAP_CLI" <command> [args]
# Windows: python "%USERPROFILE%\.agents\skills\sap-adt-cli\scripts\sap_adt_cli.py"

Unconfigured → CONFIG_MISSING (exit 2). First-use credential collection and non-interactive configure: read references/credentials.md. Multiple systems/profiles (--profile, SAP_PROFILE, PRD danger of global flags): read references/profiles.md.

Output contract

Every read command prints a JSON envelope (source commands print plain ABAP by default):

{ "ok": true, "format_version": 1, "command": "get-table", "profile": "dev",
  "object": {"type": "table", "name": "VBAK"},
  "kind": "fields", "data": { }, "meta": {"row_count": 0} }

Errors on stderr: { "ok": false, …, "error": {"code","message","http_status","hint"} }.

kindcommandsdata shape
sourceget-program/class/function-group/function/include/interface/cds-view/type-group{source, line_count}; default output = verbatim source
fieldsget-table, get-structure{fields:[{name,type,length,decimals,is_key,not_null}]} (S/4 DDL: length null for element refs, listed in meta.unparsed_types; no description key)
objectssearch-object, get-package, where-used{objects:[{name,type,uri,package,description}]}; where-used may add optional usage_line/usage_uri
rowsrun-sql{columns:[{name,type}], rows:[[…]]}
recordslist-transports{transports:[{trkorr,description,status,status_text,owner,target,tasks}]}
findingssyntax-check, run-unit-test, run-atc{findings:[{severity,text,line,uri}]}
scalarget-type-info, get-transactionobject dictionary; type info has resolved_as: domain|dataelement
capabilitiesdiscovery{collections:[{href,title,content_types}]} (Atom discovery; use credentials doctor --coverage for the command matrix)

doctor --coverage "available" only guarantees the resource root exists — discovery omits sub-paths, HTTP methods and required content types. Sub-path moves, GET→POST and content-type mismatches are invisible to it and only real-machine fixture regression catches them.

Format selection: source defaults to text (byte-identical, safe to redirect), every other kind defaults to json. Global -f/--format json|text|xml or SAP_ADT_FORMAT (flag wins). --format xml returns the original ADT payload (escape hatch for parsers). Empty results are still ok:true, row_count:0, exit 0. Full examples: references/examples.md.

Error codes and exit tiers

ExitMeaningCodes
0success, including empty results
1operational, retryable (9 codes; incl. two release results)CSRF_EXPIRED, SERVICE_NOT_ACTIVE, BAD_REQUEST, SERVER_ERROR, LOCKED_BY_OTHER, NETWORK_ERROR, PARSE_FAILED, RELEASE_UNVERIFIED, RELEASE_REJECTED
2configuration / credentialsCONFIG_MISSING, PROFILE_NOT_FOUND, AUTH_FAILED
3policy refusal / operation did not happen — do not retryWRITE_DISABLED, TRANSPORT_DISABLED, CONFIRM_REQUIRED, USER_ABORTED, DML_REJECTED
4requested object does not existOBJECT_NOT_FOUND

All 18 codes. RELEASE_UNVERIFIED = release sent, final status unknown (readback timeout) — never re-release, verify in SE09/SE10; RELEASE_REJECTED = still status D / check failed. Our errors are JSON envelopes on stderr; Click usage errors are plain text (also exit 2 — distinguish by content). OBJECT_NOT_FOUND needs a 404 ExceptionResourceNotFound body (404 "No suitable resource" is BAD_REQUEST); non-CSRF 403 is AUTH_FAILED.

Command index

CommandOne-linerkind
statusactive profile, switches, config source (plain text)
configure [--profile N]save a profile (flags = non-interactive JSON; no flags = interactive wizard)
profile list|use|removemanage environments
credentials set|forget|status|doctor [--coverage]keystore management; doctor --coverage = command/resource matrix for this system
discoveryADT resources this system exposes (href/title/content-types)capabilities
get-program / -class / -function-group / -function --group / -include / -interface / -cds-view / -type-groupread sourcesource
get-table / get-structure <N>DDIC fields (DDL on S/4)fields
get-type-info <N>domain/data element with resolved_asscalar
get-transaction <CODE>package/application/facetsscalar
search-object "<PATTERN>" [--max-results N]wildcard search (*)objects
get-package <N>package contentsobjects
where-used <TYPE> <N> [--group G] [--max-results N]referencing objectsobjects
syntax-check <TYPE> <N> [--group G]findings; hard errors exit 1, warnings exit 0findings
run-unit-test <N> [--type T] [--risk-level harmless|dangerous|critical] [--duration short|medium|long] [--fail-on error|warning|info|never]ABAP Unit; harmless default (read-only); meta no_tests_found distinguishes "no tests" (total 0) from "all passed"findings
run-atc <N> [--type T] [--variant V] [--fail-on …]Static ATC checks (no gate); stable check_id/message_id, priority 1/2/3→error/warning/info; exempted findings auditable but never failfindings
run-sql "<SELECT>" [--max-rows N]Open SQL preview; SELECT only; --max-rows (rowNumber) is the hard cap and overrides SQL UP TO N ROWS — conflicts flagged in meta.row_limit_conflictrows
list-transports [--user U] [--status D|R]transport tree (read-only)records
write-source <TYPE> <N> --file F [--group G] [--transport T] [--activate] [--yes]stateful _action=LOCK→PUT→_action=UNLOCK in finally (real-verified Basis 7.56, 2026-09-16)gated
activate <TYPE> <N> [--group G] [--yes]?method=activate; no lock/shared session needed, succeeds in a separate process (real-verified Basis 7.56)gated
create-transport --package P --description D --ref URI [--yes]CreateCorrectionRequest ASX (DEVCLASS+REF required, $TMP=local); real-verified Basis 7.56, 2026-09-17gated
release-transport <TRKORR> [--dry-run] [--yes]release with TRSTATUS readback (2s poll, 120s); dry-run = preflight onlygated

Unit risk levels: dangerous/critical tests execute ABAP that may modify business data — require allow_write, a risk/object/data-change warning in [y/N], and are hard-refused on environment=prd. Empty runResultok:true, no_tests_found:true, total:0 (same for an alert-only defective test class, with warning findings).

Safety gates (do not weaken)

  • Capabilities are profile-scoped (configure --allow-write/--allow-transport write the profile section). The top-level --global-allow-* switches are a legacy fallback used only when the profile declares neither. Agents should always use the profile scope.
  • Each profile has environment: dev|qas|prd (--environment; default inferred loosely from the name: contains prd/prod→prd, qas/qa→qas, else dev; reproduce matches prod by design — a false prd only blocks, use --environment dev).
  • environment=prd hard-refuses ALL writes (write/activate/create/release, dangerous/critical Unit): no prompt, no flag override → exit 3; inferred-prd hints explain how to override via --environment dev.
  • Off flags → exit 3 before any HTTP call. Every write/create/release then shows a preview and requires a fresh [y/N], used for one operation only, never cached/reused even within the conversation.
  • Non-interactive stdin without --yesCONFIRM_REQUIRED (exit 3); N → USER_ABORTED.
  • Env/.env writes: SAP_ALLOW_WRITE/TRANSPORT=true requires SAP_ENVIRONMENT explicitly (nothing to infer from), else CONFIG_MISSING exit 3; …=prd refuses.
  • run-sql blocks non-SELECT DML before sending (DML_REJECTED, exit 3).
  • write-source always unlocks (_action=UNLOCK, handle via query) in finally; release-transport cannot be undone. Write-side 2xx = "accepted" only — release/activate/unlock need independent readback (references/adt_api.md top rule); foreign-context unlock is a silent 200 no-op.

References (load on demand)

Layers: this file = operational contract; references/ = task detail; the bundled docs folder = project history for humans. Links point only inward, and no behavior rule is more than one hop away.

  • references/credentials.md — first-time setup, keystore backends, .env/env overrides, credential commands
  • references/profiles.md — multi-environment management and agent rules
  • references/examples.md — all command examples and workflows
  • references/adt_api.md — endpoint reference incl. verified S/4HANA 2021 protocol facts (old→new, dated)

Signals

GitHub stars
36
Forks
16
Last commit
Sep 2026

ahel review

  • K1binfo
    installs-packages (in scripts/lib/keystore/file_store.py)
  • K1binfo
    installs-packages (in scripts/lib/keystore/keyring_store.py)
  • K1binfo
    installs-packages (in README.md)
  • K7info
    secret-appetite (in README.md)
  • K1binfo
    installs-packages (in README.zh-CN.md)

Automated review, not a security audit. Ruleset v1+k2.

Advanced
Catalog kind
skill
Gateway key
sap-adt-cli
Source
github.com/shrek-abaper/sap-engineering-skill