# ๐ฐ๏ธ `@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.
[](https://www.npmjs.com/package/@saptools/cf-explorer)
[](https://nodejs.org)
[](https://www.typescriptlang.org)
[](https://docs.cloudfoundry.org/cf-cli/)
[Why](#-why) โข [Install](#-install) โข [Quick Start](#-quick-start) โข [CLI](#-cli) โข [API](#-typescript-api) โข [Sessions](#-persistent-sessions) โข [Safety](#-safety-model)
---
## โจ Why
Cloud Foundry app exploration often starts with a slow, repetitive loop:
1. Open `cf ssh`.
2. Find where the deployed app actually lives.
3. Search filenames and content.
4. Inspect the exact runtime line.
5. Turn the match into a useful file/line location.
6. 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
```bash
npm install -g @saptools/cf-explorer
```
---
## ๐ Credentials
`cf-explorer` uses the same environment variables as the other SAP tools
packages:
```bash
export SAP_EMAIL="user@example.com"
export SAP_PASSWORD="your-password"
```
Optional overrides:
```bash
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_EMAIL` and `SAP_PASSWORD` are stripped from normal child-process env
after resolution;
- `cf auth` should 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.
```bash
cf-explorer roots \
--region region-key \
--org org-name \
--space space-name \
--app app-name
```
```bash
cf-explorer ls \
--region region-key \
--org org-name \
--space space-name \
--app app-name \
--path /app-root
```
```bash
cf-explorer grep \
--region region-key \
--org org-name \
--space space-name \
--app app-name \
--root /app-root \
--text "needle"
```
```bash
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:
```bash
cf-explorer inspect-candidates \
--region region-key \
--org org-name \
--space space-name \
--app app-name \
--text "needle"
```
---
## ๐งฐ CLI
Check the installed CLI version:
```bash
cf-explorer --version
```
All read/discovery commands accept:
| Flag | Description |
| --- | --- |
| `--region ` | CF region key resolved through the SAP tools region catalog |
| `--org ` | CF org |
| `--space ` | CF space |
| `--app ` | CF app |
| `--process ` | CF process name, default `web` |
| `--instance ` | One app process instance; defaults to the first/default instance (`0`) |
| `--timeout ` | Command timeout |
| `--max-files ` | Result limit for path-like discovery and inspect file-candidate scans |
| `--max-matches ` | Result limit for `grep`, `session grep`, and `inspect-candidates` content matches |
| `--max-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
```bash
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
```bash
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:
```json
{
"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.
```bash
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
cf-explorer session ls --session-id --path /app-root
cf-explorer session grep --session-id --root /app-root --text "needle"
cf-explorer session view --session-id --file /app-root/src/handler.js --line 42
cf-explorer session stop --session-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:
```text
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 -i -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
```ts
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:
```ts
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 `view` output 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-ssh` and `cf restart` only 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:
```text
~/.saptools/cf-explorer/
sessions.json
sessions.lock
sockets/
.sock
cf-homes/
/
tmp/
/
logs/
```
Rules:
- `sessions.json` stores only non-secret session metadata.
- Persistent `CF_HOME` folders 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:
```ts
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`](https://www.npmjs.com/package/@saptools/cf-debugger): opens Node debugging tunnels through CF SSH.
- [`@saptools/cf-files`](https://www.npmjs.com/package/@saptools/cf-files): reads CF env and pulls individual remote files.
---
## ๐จโ๐ป Author
**dongtran** โจ
## ๐ License
MIT
---
Made with โค๏ธ to make your work life easier!