npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@saptools/cf-explorer

v0.2.11

Published

Explore files inside SAP BTP Cloud Foundry app containers with safe read-only SSH workflows.

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.

npm version node typescript cloud foundry

WhyInstallQuick StartCLIAPISessionsSafety


✨ 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 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_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.

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 | | --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-name

Use --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 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 });

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 view output 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-ssh or cf restart;
  • prompt for confirmation unless --yes is 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.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.

📤 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


👨‍💻 Author

dongtran

📄 License

MIT


Made with ❤️ to make your work life easier!