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.
No other account needed.
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"} }.
| kind | commands | data shape |
|---|---|---|
source | get-program/class/function-group/function/include/interface/cds-view/type-group | {source, line_count}; default output = verbatim source |
fields | get-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) |
objects | search-object, get-package, where-used | {objects:[{name,type,uri,package,description}]}; where-used may add optional usage_line/usage_uri |
rows | run-sql | {columns:[{name,type}], rows:[[…]]} |
records | list-transports | {transports:[{trkorr,description,status,status_text,owner,target,tasks}]} |
findings | syntax-check, run-unit-test, run-atc | {findings:[{severity,text,line,uri}]} |
scalar | get-type-info, get-transaction | object dictionary; type info has resolved_as: domain|dataelement |
capabilities | discovery | {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
| Exit | Meaning | Codes |
|---|---|---|
| 0 | success, including empty results | — |
| 1 | operational, 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 |
| 2 | configuration / credentials | CONFIG_MISSING, PROFILE_NOT_FOUND, AUTH_FAILED |
| 3 | policy refusal / operation did not happen — do not retry | WRITE_DISABLED, TRANSPORT_DISABLED, CONFIRM_REQUIRED, USER_ABORTED, DML_REJECTED |
| 4 | requested object does not exist | OBJECT_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
| Command | One-liner | kind |
|---|---|---|
status | active profile, switches, config source (plain text) | — |
configure [--profile N] | save a profile (flags = non-interactive JSON; no flags = interactive wizard) | — |
profile list|use|remove | manage environments | — |
credentials set|forget|status|doctor [--coverage] | keystore management; doctor --coverage = command/resource matrix for this system | — |
discovery | ADT resources this system exposes (href/title/content-types) | capabilities |
get-program / -class / -function-group / -function --group / -include / -interface / -cds-view / -type-group | read source | source |
get-table / get-structure <N> | DDIC fields (DDL on S/4) | fields |
get-type-info <N> | domain/data element with resolved_as | scalar |
get-transaction <CODE> | package/application/facets | scalar |
search-object "<PATTERN>" [--max-results N] | wildcard search (*) | objects |
get-package <N> | package contents | objects |
where-used <TYPE> <N> [--group G] [--max-results N] | referencing objects | objects |
syntax-check <TYPE> <N> [--group G] | findings; hard errors exit 1, warnings exit 0 | findings |
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 fail | findings |
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_conflict | rows |
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-17 | gated |
release-transport <TRKORR> [--dry-run] [--yes] | release with TRSTATUS readback (2s poll, 120s); dry-run = preflight only | gated |
Unit risk levels:
dangerous/criticaltests execute ABAP that may modify business data — requireallow_write, a risk/object/data-change warning in[y/N], and are hard-refused onenvironment=prd. EmptyrunResult→ok: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-transportwrite 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: containsprd/prod→prd,qas/qa→qas, else dev;reproducematchesprodby 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
--yes→CONFIRM_REQUIRED(exit 3); N →USER_ABORTED. - Env/.env writes:
SAP_ALLOW_WRITE/TRANSPORT=truerequiresSAP_ENVIRONMENTexplicitly (nothing to infer from), elseCONFIG_MISSINGexit 3;…=prdrefuses. run-sqlblocks non-SELECT DML before sending (DML_REJECTED, exit 3).- write-source always unlocks (
_action=UNLOCK, handle via query) infinally; 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 commandsreferences/profiles.md— multi-environment management and agent rulesreferences/examples.md— all command examples and workflowsreferences/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