@saptools/cf-explorer
v0.2.11
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 the app by keeping file discovery read-only by default.
🚀 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 |
| ssh-status / enable-ssh / restart | Explicit SSH lifecycle commands with confirmation |
📦 Install
npm install -g @saptools/cf-explorer🔐 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 |
| --all-instances | Run supported read-only commands across running instances |
| --timeout <seconds> | Command timeout |
| --max-files <count> | Result limit for path-like outputs |
| --max-bytes <bytes> | Output byte limit |
| --json / --no-json | Structured JSON (default) or compact human-readable output |
🪄 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
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"
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"Suggested candidate shape:
{
"instance": 0,
"bp": "/app-root/src/handler.js",
"remoteRoot": "/app-root",
"line": 42,
"confidence": "high",
"reason": "content match"
}🔁 SSH Lifecycle
Lifecycle commands can change app state, so they are never run implicitly by read-only discovery commands.
cf-explorer ssh-status --region region-key --org org-name --space space-name --app app-name
cf-explorer enable-ssh --region region-key --org org-name --space space-name --app app-name
cf-explorer restart --region region-key --org org-name --space space-name --app app-name
cf-explorer prepare-ssh --region region-key --org org-name --space space-name --app app-nameUse --yes only when you intentionally want non-interactive lifecycle changes:
cf-explorer prepare-ssh \
--region region-key \
--org org-name \
--space space-name \
--app app-name \
--yes🧵 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 });Lifecycle APIs require explicit confirmation:
await explorer.prepareSsh({ confirmImpact: true });
await explorer.restartApp({ confirmImpact: true });🛡️ 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.
Explicit lifecycle commands:
- may run
cf enable-sshorcf restart; - prompt for confirmation unless
--yesis provided; - are app-level operations, not per-instance operations;
- stop or mark 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.
📤 JSON Output
Every JSON response includes metadata:
interface ExplorerMeta {
target: {
region: string;
org: string;
space: string;
app: string;
};
instance?: number;
durationMs: number;
truncated: boolean;
}Examples:
{
"meta": {
"target": {
"region": "region-key",
"org": "org-name",
"space": "space-name",
"app": "app-name"
},
"instance": 0,
"durationMs": 214,
"truncated": false
},
"matches": [
{
"instance": 0,
"path": "/app-root/src/handler.js",
"line": 42
}
]
}🧯 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 | App SSH is not currently enabled |
| 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 |
| LIFECYCLE_CONFIRMATION_REQUIRED | A state-changing command needs confirmation |
| 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!
