@saptools/cf-explorer
Searches the files of an app deployed to SAP BTP Cloud Foundry over cf ssh, finding the app root, grepping the deployed code and turning matches into file and line candidates.
Finding the code an app on Cloud FoundrySAP BTP, Cloud Foundry RuntimeThe Cloud Foundry runtime of SAP BTP: memory for applications in a Cloud Foundry org, run from SAP's Java, Node.js and Python buildpacks or community buildpacks and managed with the standard Cloud Foundry tools.sap official, btp is actually running is, by hand, a loop of cf ssh, locating the app root, searching and reading the line, repeated after every wrong guess. cf-explorer makes each step a bounded command (roots, ls, find, grep, view), and inspect-candidates turns a text match into suggested breakpoint locations with their remote root. For many reads, session start keeps one SSH connection open behind a local broker, and the same commands are available as a typed Node.js API. Commands are built from templates that reject arbitrary shell text, and none uploads, edits or deletes a file; the one change it makes is to enable SSH and restart the app when SSH is disabled.
Facts
Where it sits
Who is behind it
In the atlas: maintains 18, contributes to 18@dongitranmaintainer
What it declares
Every artifact
- Type
- CLI
- Origin
- Independently maintained
- Runs on
- Cloud Foundry
As a CLI
- Distributed through
- npm
Connections
Seeing what running code did between input and result
Cloud Foundry runtime of SAP BTP for polyglot applications
Download
Install
npm add @saptools/cf-explorerDerived from the package name. No recipe recorded; the README has the configuration.
Architecture
Download
README
quoted from dongitran/saptools under MIT, read🛰️ @saptools/cf-explorer
Fast, safe Cloud Foundry runtime discovery for SAP BTP workflows.
Find app roots, search deployed code, inspect line context, reuse SSH sessions, and produce precise file/line candidates for people, scripts, and downstream tools through a beautiful CLI and a typed Node.js API.
✨ Why
Cloud Foundry app exploration often starts with a slow, repetitive loop:
- Open
cf ssh. - Find where the deployed app actually lives.
- Search filenames and content.
- Inspect the exact runtime line.
- Turn the match into a useful file/line location.
- Repeat after every wrong guess.
cf-explorer turns that loop into a structured workflow:
- 🔎 Discover deployed app roots and runtime files.
- 🧭 Map grep results into reusable file/line candidates.
- ⚡ Reuse one SSH-backed session when many reads are needed.
- 🧩 Import the same behavior from another Node.js project.
- 🛡️ Protect inputs with bounded commands; SSH is enabled and restarted automatically when needed.
🚀 What It Does
| Capability | Purpose |
|---|---|
roots | Locate likely app roots with bounded read-only probes |
instances | Show app process instance indexes and status |
ls | List direct children under a known remote directory |
find | Search filenames under a remote root |
grep | Search remote file content and return path + line |
view | Print a bounded line window around a remote file location |
inspect-candidates | Suggest candidate paths, line numbers, and root mappings |
session start | Keep one SSH-backed broker alive for fast repeated reads |
📦 Install
npm install -g @saptools/cf-explorer
Updates
Every command first checks npm for a newer @saptools/cf-explorer (at most once an hour, one small request
with a 2-second timeout) and, when one exists, installs that exact version with the package manager
that owns the running binary and re-runs the command you typed on the new version. Both steps are
announced on stderr; nothing is printed when the install is already current:
cf-explorer: updating 0.5.0 -> 0.6.0 ...
cf-explorer: updated to 0.6.0; re-running the command
If the install cannot complete, one stderr line gives the manual command and the command runs on the
installed version; that version is not retried for a day. cf-explorer self-update forces the check and
install now; cf-explorer self-update --check only reports.
| Control | Effect |
|---|---|
SAPTOOLS_AUTO_UPDATE=on|notify|off | on (default) installs and re-runs; notify prints the manual command once per version; off never checks. Applies to every @saptools CLI. |
CF_EXPLORER_AUTO_UPDATE | same values, this CLI only; wins over the global variable |
SAPTOOLS_UPDATE_INTERVAL_MINUTES | minutes between checks (default 60; 0 checks on every run) |
SAPTOOLS_NPM_REGISTRY | registry to check and install from (default: npm's configured registry, then npmjs) |
SAPTOOLS_UPDATE_DEBUG=1 | explain on stderr why nothing happened |
The updater switches itself off in CI (CI set), under NODE_ENV=test or NO_UPDATE_NOTIFIER, when
the binary runs from a source checkout, an npm link or an npx cache, and inside the re-run itself.
It never writes to stdout, never asks for input, never uses sudo, and never moves onto a prerelease.
Its state lives in ~/.saptools/updates/.
🔐 Credentials
cf-explorer uses the same environment variables as the other SAP tools
packages:
export SAP_EMAIL="user@example.com"
export SAP_PASSWORD="your-password"
Optional overrides:
export CF_EXPLORER_CF_BIN="/path/to/cf"
export CF_EXPLORER_HOME="$HOME/.saptools/cf-explorer"
Credential handling is intentionally conservative:
- credentials are resolved from env by default;
SAP_EMAILandSAP_PASSWORDare stripped from normal child-process env after resolution;cf authshould receive credentials through scoped child env variables;- secrets, tokens, and remote file contents are not written to session state.
⚡ Quick Start
Find a root, search for code, inspect context, then reuse the location in your next command, script, editor, or debugging tool.
cf-explorer roots \
--region region-key \
--org org-name \
--space space-name \
--app app-name
cf-explorer ls \
--region region-key \
--org org-name \
--space space-name \
--app app-name \
--path /app-root
cf-explorer grep \
--region region-key \
--org org-name \
--space space-name \
--app app-name \
--root /app-root \
--text "needle"
cf-explorer view \
--region region-key \
--org org-name \
--space space-name \
--app app-name \
--file /app-root/src/handler.js \
--line 42 \
--context 6
Generate reusable file/line candidates:
cf-explorer inspect-candidates \
--region region-key \
--org org-name \
--space space-name \
--app app-name \
--text "needle"
🧰 CLI
Check the installed CLI version:
cf-explorer --version
All read/discovery commands accept:
| Flag | Description |
|---|---|
--region <key> | CF region key resolved through the SAP tools region catalog |
--org <name> | CF org |
--space <name> | CF space |
--app <name> | CF app |
--process <name> | CF process name, default web |
--instance <index> | One app process instance; defaults to the first/default instance (0) |
--timeout <seconds> | Command timeout |
--max-files <count> | Result limit for path-like discovery and inspect file-candidate scans |
--max-matches <count> | Result limit for grep, session grep, and inspect-candidates content matches |
--max-bytes <bytes> | Output byte limit |
--follow-symlinks | Follow symlinked directories during find/grep traversal for pnpm-style layouts |
Single-shot and session commands write compact human-readable results directly
to stdout. If a discovery result exceeds a count limit, the CLI keeps stdout
unchanged and writes a truncation warning to stderr. A single-shot response that
reaches its byte limit produces the same warning; a persistent-session request
instead fails with OUTPUT_LIMIT_EXCEEDED. Increase --max-matches,
--max-files, or --max-bytes, as appropriate, and retry.
🪄 Discovery
cf-explorer roots --region region-key --org org-name --space space-name --app app-name
cf-explorer instances --region region-key --org org-name --space space-name --app app-name
cf-explorer ls --region region-key --org org-name --space space-name --app app-name --path /app-root --pattern "*helper*"
cf-explorer find --region region-key --org org-name --space space-name --app app-name --root /app-root --name "*handler*.js"
cf-explorer grep --region region-key --org org-name --space space-name --app app-name --root /app-root --text "needle" --max-matches 20 --follow-symlinks
cf-explorer view --region region-key --org org-name --space space-name --app app-name --file /app-root/src/handler.js --line 42 --context 8
🎯 File/Line Candidates
cf-explorer inspect-candidates \
--region region-key \
--org org-name \
--space space-name \
--app app-name \
--text "needle"
inspect-candidates prints sectioned roots, content matches, and suggested
breakpoints. Use --max-matches to bound breakpoint-oriented search output.
The typed API can additionally populate its files array when called with
includeFiles: true; compact CLI output does not render that array.
Suggested breakpoint shape from the typed API:
{
"instance": 0,
"bp": "/app-root/src/handler.js",
"remoteRoot": "/app-root",
"line": 42,
"confidence": "high",
"reason": "content match"
}
🔁 SSH Access
SSH-backed commands automatically enable app SSH and restart the app when SSH is disabled.
🧵 Persistent Sessions
One-shot mode is simple: each CLI invocation opens a fresh cf ssh, runs one
bounded command, and exits. Persistent mode is for deeper exploration where that
round trip becomes the bottleneck.
cf-explorer session start \
--region region-key \
--org org-name \
--space space-name \
--app app-name \
--instance 0 \
--idle-timeout 900 \
--max-lifetime 3600
cf-explorer session list
cf-explorer session status --session-id <id>
cf-explorer session ls --session-id <id> --path /app-root
cf-explorer session grep --session-id <id> --root /app-root --text "needle"
cf-explorer session view --session-id <id> --file /app-root/src/handler.js --line 42
cf-explorer session stop --session-id <id>
--idle-timeout and --max-lifetime are optional seconds-based guards for
the local broker process.
🛰️ How Session Reuse Works
Persistent sessions use a local broker process:
CLI command
-> local IPC socket
-> cf-explorer broker
-> live cf ssh child process
-> remote sh
The broker is the only process that owns the live SSH stdin/stdout streams.
sessions.json is only an index; it is not the command channel.
The broker:
- opens one
cf ssh --disable-pseudo-tty --process <process> -i <index> -c sh; - performs a startup handshake;
- accepts newline-delimited JSON requests over local IPC;
- validates each request against known explorer commands;
- queues one remote command at a time;
- wraps remote output in sentinel markers;
- enforces timeouts, output limits, and stale-session cleanup.
🧑💻 TypeScript API
import {
attachExplorerSession,
createExplorer,
listExplorerSessions,
startExplorerSession,
stopExplorerSession,
} from "@saptools/cf-explorer";
const explorer = await createExplorer({
target: {
region: "region-key",
org: "org-name",
space: "space-name",
app: "app-name",
},
});
const rootsResult = await explorer.roots();
const root = rootsResult.roots[0] ?? "/app-root";
const entries = await explorer.ls({ path: root, instance: 0 });
const matches = await explorer.grep({
root,
text: "needle",
instance: 0,
});
await explorer.dispose();
Broker-backed session:
const session = await startExplorerSession({
target: {
region: "region-key",
org: "org-name",
space: "space-name",
app: "app-name",
},
instance: 0,
});
const attached = await attachExplorerSession(session.sessionId);
const entries = await attached.ls({ path: "/app-root" });
const result = await attached.grep({ root: "/app-root", text: "needle" });
await stopExplorerSession({ sessionId: session.sessionId });
🛡️ Safety Model
cf-explorer is designed around a narrow safety boundary.
Read-only discovery commands:
- generate remote commands from templates;
- reject arbitrary shell text;
- quote user-provided values;
- reject NUL bytes, newlines, unsafe roots, and invalid instance selectors;
- enforce output, file, context, depth, and timeout limits;
- prune noisy folders such as
node_modules,.git,dist,build, and.cache; - omit grep previews unless explicitly requested. Preview and
viewoutput can contain remote file content, so it is returned only to the caller and is not stored in session state.
Automatic SSH access:
- checks SSH before opening SSH-backed reads;
- runs
cf enable-sshandcf restartonly when SSH is disabled; - marks related persistent sessions stale after restart.
No command uploads, edits, deletes, installs packages, changes permissions, or opens an unrestricted interactive shell.
🗂️ Local State
All package-owned files live under:
~/.saptools/cf-explorer/
sessions.json
sessions.lock
sockets/
<session-id>.sock
cf-homes/
<session-id>/
tmp/
<run-id>/
logs/
Rules:
sessions.jsonstores only non-secret session metadata.- Persistent
CF_HOMEfolders are treated as sensitive and removed on session stop. - Temp files are deleted after one-shot workflows.
- Logs must not contain credentials or remote file contents.
- Stale sessions are pruned by hostname, broker PID, SSH PID, and socket health.
📤 Result Metadata
Typed API discovery results include metadata:
interface ExplorerMeta {
target: {
region: string;
org: string;
space: string;
app: string;
};
process: string;
instance?: number;
durationMs: number;
truncated: boolean;
}
meta.truncated is true when a returned one-shot response reached maxBytes
or when a returned one-shot or session result exceeded the requested maxFiles
or maxMatches window. The returned data is incomplete; increase the
corresponding limit and retry. The CLI exposes the same condition through its
stderr warning while keeping result stdout compact. Persistent-session requests
that exceed maxBytes fail with OUTPUT_LIMIT_EXCEEDED instead of returning
partial result metadata.
🧯 Error Codes
| Code | Meaning |
|---|---|
MISSING_CREDENTIALS | SAP_EMAIL or SAP_PASSWORD is missing |
UNKNOWN_REGION | Region key is not known |
CF_LOGIN_FAILED | cf api or cf auth failed |
CF_TARGET_FAILED | cf target failed |
APP_NOT_FOUND | Target app was not found |
SSH_DISABLED | SSH could not be used after automatic preparation |
INSTANCE_NOT_FOUND | Requested app process instance is unavailable |
UNSAFE_INPUT | Input failed validation |
OUTPUT_LIMIT_EXCEEDED | Remote output exceeded configured limits |
REMOTE_COMMAND_FAILED | A bounded remote command failed |
SESSION_NOT_FOUND | The requested persistent session does not exist |
SESSION_STALE | The persistent session is no longer usable |
SESSION_BUSY | Persistent broker queue is full |
BROKER_UNAVAILABLE | The broker process is not reachable |
IPC_FAILED | Local IPC request failed |
SESSION_PROTOCOL_ERROR | Persistent shell marker parsing failed |
SESSION_HANDSHAKE_FAILED | Persistent shell startup handshake failed |
SESSION_RECOVERY_FAILED | Broker could not recover the remote shell |
ABORTED | The caller aborted the operation |
🤝 Related Packages
@saptools/cf-debugger: opens Node debugging tunnels through CF SSH.@saptools/cf-files: reads CF env and pulls individual remote files.
👨💻 Author
dongtran ✨
📄 License
MIT
Made with ❤️ to make your work life easier!
History
Other 1
Notes
- Accept the self-updating releases of
@saptools/cf-syncas dependencies (range widened, no behaviour change).
Other 1
Notes
- Self-updating. Every command now checks npm for a newer
@saptools/cf-explorer(at most once an hour) and, when one exists, installs that exact version and re-runs the command on it, announcing both steps on stderr.SAPTOOLS_AUTO_UPDATE=on|notify|offcontrols it (see the README's Updates section); it is off by itself in CI, in tests, from a source checkout and inside the re-run. Newcf-explorer self-update [--check]forces the check and install now.--versionnow readspackage.jsonat runtime. Both come from the private, build-time-bundled@saptools/corepackage; the published tarball gains no runtime dependency.
- Self-updating. Every command now checks npm for a newer
Breaking 1, Fixes 1
Notes
Breaking changes
- Standardized all one-shot discovery and persistent-session result commands on compact human-readable stdout.
Bug fixes
- Made count-limited
roots,ls,find,grep, andinspect-candidatesresults probe one extra row so both one-shot and session APIs setExplorerMeta.truncatedexactly when more results exist. The same field remains true for byte-limited responses, and the CLI now warns on stderr when either limit makes a result incomplete.
Fixes 1
Notes
Bug fixes
- Stopped reporting the Linux FHS
/srvdirectory as an app root during root discovery.
- Stopped reporting the Linux FHS
Other 3
Notes
Hardening
- Rejected timeout values above Node.js timer limits across one-shot CF calls, IPC requests, persistent shell commands, broker timers, and CLI seconds-based flags.
- Validated explicit API endpoint overrides in
resolveApiEndpointwith the same control-character checks used by normalized targets.
Maintenance
- Split persistent-session CLI command registration into smaller focused helpers without changing command behavior.
Other 1
Notes
Maintenance
- Removed obsolete one-line source wrapper modules and pointed internal tests and package-root exports at the focused implementation folders.
Other 1
Notes
Maintenance
- Reorganized source modules into focused CLI, CF, discovery, broker, session, and core folders while preserving existing public imports and runtime behavior.
Other 1
Notes
Maintenance
- Split persistent SSH shell handling into a focused broker module with direct unit coverage, keeping persistent-session behavior unchanged.
Other 1
Notes
Maintenance
- Split CLI human-output rendering into a focused module with unit coverage, keeping command behavior unchanged while reducing the main CLI file size.
Features 2
Notes
Improvements
- Added root
--versionoutput for quick installed CLI checks. - Added
session start --idle-timeoutand--max-lifetimeflags to expose the existing broker timer controls from the CLI.
- Added root
Features 1
Notes
Improvements
- Added read-only
lscommands for one-level remote directory listings in one-shot and persistent-session workflows.
- Added read-only
Features 1, Fixes 2
Notes
Bug fixes
- Dropped incomplete trailing protocol rows from truncated one-shot remote output so partial paths are not reported as real discovery results.
- Parsed only the timestamp-like
sincecolumn from detailed CF app instance rows instead of storing later resource columns in the same field.
Improvements
- Added
--timeoutand--max-bytesto persistent-session read commands, matching the existing API and broker per-request limit support.
Fixes 3
Notes
Bug fixes
- Fixed
viewoutput on Cloud Foundry containers by emitting line protocol rows directly fromawkinstead of relying onnlseparator behavior. - Changed
session stopto return structured JSON with a stopped-session count instead of a primitive number. - Fixed a broker shutdown race that could leave a stale session lock after
session stop.
- Fixed
Fixes 1, Other 2
Notes
Bug fixes
- Classified CF command failures before redacting detail text, preserving typed errors when credential values overlap with CF error keywords.
Hardening
- Added bounded IPC request/response buffering for persistent-session sockets.
- Validated broker bootstrap payloads before starting broker session work.
Fixes 3, Other 3
Notes
Bug fixes
- Preserved explicit credential passwords exactly instead of trimming leading or trailing characters.
- Propagated timeout/output limits to
cf appand lifecycle CF calls. - Tightened malformed grep/protocol numeric parsing.
Hardening
- Redacted generated one-shot SSH scripts from CF failure details.
- Validated CF runner timeout/output limits before spawning child processes.
- Handled persistent SSH shell spawn errors without an unhandled broker error.
Fixes 5
Notes
Bug fixes
- Grep parsing now treats tab-delimited output as fixed fields, so preview text cannot be mistaken for the path or line delimiter.
- Persistent broker command failures now return typed IPC errors instead of escaping from stdout event handlers.
- Persistent sessions update
lastUsedAton broker requests. - Session registration rejects duplicate explicit session ids.
- CF child-process collection now settles once across abort, timeout, spawn error, and close races.
Fixes 10, Performance 2, Other 6
Notes
Bug fixes
- IPC server now drains every newline-terminated request in a single chunk
instead of stopping after the first one (
src/ipc.ts). - Broker marks the session
staleand shuts down when the persistent SSH shell exits or stderr reportsclosed, sosession list/statusno longer reports a dead session asready(src/broker.ts). runtime.timeoutMsandruntime.maxBytesnow propagate tocf sshfor one-shot discovery commands, not just tocf api/auth/target(src/runner.ts).--no-jsonproduces concise human-readable output for every result shape instead of falling back to JSON (src/cli.ts).INSTANCE_NOT_FOUNDis raised when--all-instancesfinds no running instances and whencf sshreports an out-of-range index. The previously declared but unused error codes are now actually emitted (src/api.ts,src/cf.ts).SESSION_HANDSHAKE_FAILEDis raised when the persistent shell handshake produces unexpected output or times out (src/broker.ts).- Crashed-broker session files (
cf-homes/<id>andsockets/<id>.sock) are cleaned up the next timelistExplorerSessionsprunes the index, instead of lingering indefinitely (src/storage.ts). session startCLI no longer accepts--all-instances, matching the API contract that persistent sessions target one instance (src/cli.ts).- Broker stop responses are flushed to the socket before the process exits,
removing a 10 ms race that could drop the response (
src/ipc.ts,src/broker.ts). - Legacy grep delimiter pattern uses non-greedy path matching so a colon in
a preview no longer truncates the path (
src/parsers.ts).
Hardening
- The fallback Unix socket directory is now scoped to the current user
(
/tmp/saptools-cf-explorer-<uid>/) and ischmod 0700enforced after every IPC server bind, even when the directory pre-existed (src/paths.ts,src/ipc.ts). - Broker process clears
SAP_EMAIL,SAP_PASSWORD,CF_USERNAMEandCF_PASSWORDfromprocess.envafter CF auth so they do not leak through/proc/<pid>/environfor the broker's lifetime (src/broker.ts,src/cf.ts). - Redaction skips values shorter than 4 characters to prevent common-word
passwords from corrupting unrelated text (
src/redaction.ts). terminateProcesshelper avoids self-kill and tolerates process-not-found race conditions when stopping broker sessions (src/session.ts).
Performance
--all-instancesdiscovery commands share one prepared CF session across parallel SSH calls instead of authenticating again per instance (src/api.ts,src/runner.ts).- Root-discovery
find / -maxdepth 4now prunes noisy directories (node_modules,.git,dist,build,.cache,tmp,temp) before enumerating files (src/commands.ts).
Cleanups
- Dropped the unused
RemoteScript.operationfield; remote scripts only expose the script body now (src/commands.ts). - Coverage thresholds are gated by a documented exclusion list (broker, CLI,
index barrel, type module). Session storage and target helpers now sit
inside the gate (
vitest.config.ts).
- IPC server now drains every newline-terminated request in a single chunk
instead of stopping after the first one (
Others providing
Compare all 6 on the Debugging page →- @saptools/cf-debugger@saptools/cf-debuggerOpens a verified Node.js inspector tunnel over cf ssh to an app on SAP BTP Cloud Foundry from one command, with its own local port for each concurrent session.independent, btpNode.js inspector tunnel over cf ssh to Cloud Foundry apps
- @saptools/cf-function-trace@saptools/cf-function-traceRecords how one JavaScript function's state changes, step by step, in a Node.js process running locally or on SAP BTP Cloud Foundry, and stores the run as a redacted timeline that can be queried later.independent, btpStep-by-step state recorder for one JavaScript function in Node.js
- @saptools/cf-hana@saptools/cf-hanaA command line and TypeScript API that runs SQL against the SAP HANA Cloud database bound to a Cloud Foundry app, addressed by a region/org/space/app selector.independent, btpSQL runner against the SAP HANA Cloud database bound to a CF app
- @saptools/cf-inspector@saptools/cf-inspectorSets breakpoints, captures variable snapshots and evaluates expressions on a remote Node.js process over the Chrome DevTools Protocol from the shell, with no IDE attached.independent, btpShell debugger for remote Node.js over the Chrome DevTools Protocol
- @saptools/cf-live-trace@saptools/cf-live-traceInjects a bounded HTTP trace hook into a running Node.js app on SAP BTP Cloud Foundry and streams each request and response to the terminal as JSON.independent, btpHTTP trace hook streaming a running CF Node.js app's traffic
Details
Sources
Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.
Compatibility and provenance
History in the atlas
- First seen.
For your agent
- Ref
artifact:saptools-cf-explorer- JSON
- /artifacts/saptools-cf-explorer.json
- MCP
- Connecting an agent