@saptools/cf-explorer
v0.5.1
Published
Explore files inside SAP BTP Cloud Foundry app containers with safe read-only SSH workflows.
Maintainers
Readme
🛰️ @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 • Install • Quick Start • CLI • API • Sessions • Safety
✨ 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-explorerUpdates
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 commandIf 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="[email protected]"
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-namecf-explorer ls \
--region region-key \
--org org-name \
--space space-name \
--app app-name \
--path /app-rootcf-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 6Generate 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 --versionAll 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 shThe 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!
