Command Summaries

The Command Summaries feature records JFrog CLI command outputs into the local file system. This functionality can be used to generate a summary in the context of an entire workflow (a sequence of JFrog CLI commands) and not only in the scope of a specific command.

For example, the setup-jfrog-cli GitHub Action uses the compiled markdown to generate a comprehensive summary of the entire workflow.

Currently Supported Commands

Command summaries are organized by operation categories. Commands that perform operations in these categories will automatically generate summaries when the JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR environment variable is set.

Security Operations

Commands that perform security scanning operations generate summaries in the Security section:

jf scan

Scans files and directories for security vulnerabilities with JFrog Xray.

Example: jf scan path/to/files --watches=watch-name

📘

Note

On first run, jf scan automatically downloads the JFrog Xray Indexer and Analyzer Manager binaries (approximately 100 MB total). This download happens transparently and may take several minutes depending on your connection speed. Subsequent runs use the cached binaries.

jf build-scan

Scans build-info for security vulnerabilities.

Example: jf build-scan build-name build-number

📘

Prerequisite

The build must be selected for Xray indexing in the JFrog Platform before running this command. If it is not, the command fails with "Build <name> is not selected for indexing". To enable indexing, go to Administration > Xray > Indexed Resources in the JFrog Platform UI and add the build.

jf docker scan

Scans Docker images for security vulnerabilities with JFrog Xray.

Example: jf docker scan my-image:tag --watches=watch-name

Build-Info Operations

Commands that publish build-info generate summaries in the BuildInfo section:

jf rt build-publish

Publishes build-info to Artifactory.

Example: jf rt build-publish build-name build-number

Upload Operations

Commands that upload files or artifacts generate summaries in the Upload section:

jf rt upload

Uploads files to Artifactory repositories.

Example: jf rt upload local-path repo-name/remote-path

🚧

Known Issue

In some configurations, upload summary data is recorded to disk but the Upload section may not appear in the final markdown.md generated by jf gsm. If you do not see uploaded artifacts in the summary output, this is a known product issue being investigated.

jf docker push

Pushes Docker images to Artifactory.

Example: jf docker push my-image:tag

jf docker pull

Pulls Docker images from Artifactory. May generate summaries when configured.

Example: jf docker pull my-image:tag

Evidence Operations

Commands that collect evidence generate summaries in the Evidence section:

Evidence collection commands (specific commands depend on your JFrog Platform configuration)

Commands That Do Not Generate Summaries

Configuration and management commands do not generate command summaries, as they don't perform operational tasks that produce artifacts or scan results:

  • jf c (config) commands - Server configuration management (add, edit, show, remove, import, export, use)

Note: The command summary feature aggregates results from multiple commands executed in a workflow. Each command that supports summaries will contribute to its respective section in the final markdown output generated by jf generate-summary-markdown (or jf gsm).

Configuration

To configure Command Summaries:

  1. Set the JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR environment variable. This variable designates the directory where the data files and markdown files will be stored:

    export JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR="/path/to/output/directory"
  2. Set the JFROG_URL environment variable to your JFrog Platform URL (required for generating clickable URLs in the summary):

    export JFROG_URL="https://your-instance.jfrog.io"

    Note: Even if a server is configured with jf c add, jf gsm may not be able to read the server URL automatically. Setting JFROG_URL explicitly ensures the summary contains valid links. If JFROG_URL is not set and the URL cannot be resolved, jf gsm will exit with an error.

  3. Ensure a JFrog Platform server is configured using jf c add (required if running commands such as jf rt upload or jf rt build-publish).

🚧Warning: Files Remain After CLI ExecutionThe CLI does not automatically remove the files as they are designed to remain beyond a single execution. It is your responsibility to manage your pipelines and delete files as necessary. You can clear the entire directory of JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR that you have configured to activate this feature.

Generating the Summary

To generate the command summary markdown:

  • After running commands that support summaries, run the following command:

    jf generate-summary-markdown

    Or use the alias:

    jf gsm
📘

Note

If no summary data has been recorded yet (that is, no supported commands have been run with JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR set), jf gsm exits successfully with no output and no markdown.md is created. This is expected behavior — run at least one supported command first.

Troubleshooting

Error MessageCauseSolution
unable to generate the command summary because the output directory is not specifiedJFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR not setSet the environment variable before running commands: export JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR=/path/to/dir
no JFrog Platform URL specified, either via the --url flag or as part of the server configurationJFROG_URL environment variable is not set; jf gsm cannot read the server URL from the CLI configuration storeSet export JFROG_URL=https://your-instance.jfrog.io in your environment before running jf gsm. Note: configuring a server with jf c add alone is not sufficient.
failed to get server URL or major versionJFROG_URL is set but the URL is unreachable or the server version cannot be fetchedVerify the URL is correct and the server is reachable: curl -s $JFROG_URL/artifactory/api/system/ping

Notes for Developers

Each command execution that incorporates this feature can save data files to the file system. These files are then used to create an aggregated summary in Markdown format.

Saving data to the file system is essential because each CLI command executes in a separate context. Consequently, each command that records new data should also incorporate any existing data into the aggregated markdown. This is required because the CLI cannot determine when a command will be the last one executed in a sequence.

🚧Warning: Files Remain After CLI ExecutionThe CLI does not automatically remove the files, as they are designed to remain beyond a single execution. As a result, it is your responsibility to manage your pipelines and delete files as necessary. You can clear the entire directory of JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR that you have configured to activate this feature.

How to Implement

To implement a new command summary:

  1. Implement the CommandSummaryInterface:
type CommandSummaryInterface interface {
   GenerateMarkdownFromFiles(dataFilePaths []string) (finalMarkdown string, err error)
}
  1. Record data during runtime:

    // Check if summaries should be recorded
    if !commandsummary.ShouldRecordSummary() {
        return nil
    }
    
    // Initialize your implementation
    commandSummary, err := commandsummary.New(&MyCommandStruct{}, "CommandName")
    if err != nil {
        return err
    }
    
    // Record the data
    return commandSummary.Record(data)

The GenerateMarkdownFromFiles function needs to process multiple data files, which are the results of previous command executions, and generate a single markdown string content. As each CLI command has its own context, the entire markdown must be regenerated with the newly added results each time.

Data File Format

Data files are stored in JSON format. Use the helper function to unmarshal data:

commandsummary.UnmarshalFromFilePath(path, &yourDataStruct)

Example Implementation

package mycommand

import (
    "fmt"
    "strings"

    "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/commandsummary"
)

// Step 1. Implement the CommandSummaryInterface
type CommandStruct struct{}

type singleRecordedObject struct {
    Name   string `json:"name"`
    Status string `json:"status"`
}

func (cs *CommandStruct) GenerateMarkdownFromFiles(dataFilePaths []string) (markdown string, err error) {
    // Aggregate all the results into a slice
    var recordedObjects []*singleRecordedObject
    for _, path := range dataFilePaths {
        var singleObject singleRecordedObject
        if err = commandsummary.UnmarshalFromFilePath(path, &singleObject); err != nil {
            return "", err
        }
        recordedObjects = append(recordedObjects, &singleObject)
    }

    // Create markdown
    var results strings.Builder
    results.WriteString("# Command Summary\n\n")
    results.WriteString("| Name | Status |\n")
    results.WriteString("|------|--------|\n")
    for _, obj := range recordedObjects {
        results.WriteString(fmt.Sprintf("| %s | %s |\n", obj.Name, obj.Status))
    }

    markdown = results.String()
    return
}

// Step 2. Record data during runtime
func recordCommandSummary(data any) (err error) {
    if !commandsummary.ShouldRecordSummary() {
        return nil
    }

    commandSummaryImplementation, err := commandsummary.New(&CommandStruct{}, "CommandName")
    if err != nil {
        return err
    }

    return commandSummaryImplementation.Record(data)
}

How Does It Work?

Each command that implements the CommandSummaryInterface will have its own subdirectory inside the JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR/jfrog-command-summary directory.

Every subdirectory will house data files (in JSON format), each one corresponding to a command recording, along with a markdown file that has been created from all the data files. The function you implement is responsible for processing all the data files within its subdirectory and generating a markdown string.

The directory structure looks like this:

$JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR/
└── jfrog-command-summary/
    │
    ├── security/
    │   ├── docker-scan/
    │   │   └── <numeric-id>-data
    │   ├── build-scan/
    │   │   └── <numeric-id>-data
    │   ├── binaries-scans/
    │   │   └── <sha1-hash>
    │   ├── sarif-reports/
    │   │   ├── <numeric-id>.sarif
    │   │   └── final.sarif
    │   └── markdown.md
    │
    ├── build-info/
    │   ├── <numeric-id>-data
    │   ├── <numeric-id>-data
    │   └── markdown.md
    │
    ├── upload/
    │   ├── <numeric-id>-data
    │   ├── <numeric-id>-data
    │   └── markdown.md
    │
    ├── evidence/
    │   ├── <numeric-id>-data
    │   └── markdown.md
    │
    └── markdown.md
📘

Note

Data files use internal numeric IDs or content-addressed hashes as names, not sequential human-readable names. Do not rely on the file names in scripts — use the directory structure and markdown.md files instead.

Section Index Types

For security-related summaries, the following index types are available:

Index ConstantDirectory NameUsed By
BinariesScanbinaries-scansjf scan
BuildScanbuild-scanjf build-scan
DockerScandocker-scanjf docker scan
SarifReportsarif-reportsAll security scans

Complete Example Workflow

#!/bin/bash

# 1. Set the output directory
export JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR="/tmp/jfrog-summaries"
mkdir -p "$JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR"

# 2. Set the JFrog Platform URL (required for jf gsm to generate valid links)
#    This must be set even if a server is configured via 'jf c add'
export JFROG_URL="https://my-instance.jfrog.io"

# 3. Configure server (if not already done)
jf c add my-server \
    --url="$JFROG_URL" \
    --access-token="$JFROG_ACCESS_TOKEN" \
    --interactive=false

jf c use my-server

# 4. Run commands that generate summaries
jf rt upload "build/*.jar" my-repo/libs/
jf rt build-publish my-build 1

# 5. Run security scans
#    Note: 'jf build-scan' requires the build to be selected for Xray indexing
#    in Administration > Xray > Indexed Resources before this will succeed.
#    Note: 'jf docker scan' requires a Docker image to be available locally.
#    Note: First run of any scan command downloads ~100 MB of Xray binaries.
jf docker scan my-image:tag
jf build-scan my-build 1

# 6. Generate the summary markdown
jf gsm

# 7. View the results
echo "=== Summary Generated ==="
cat "$JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR/jfrog-command-summary/markdown.md"

# 8. Cleanup (optional)
# rm -rf "$JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR"

GitHub Actions Integration

name: Build and Scan with Summary

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up JFrog CLI
        uses: jfrog/setup-jfrog-cli@v4
        with:
          version: latest
        env:
          JF_URL: ${{ secrets.JF_URL }}
          JF_ACCESS_TOKEN: ${{ secrets.JF_ACCESS_TOKEN }}

      - name: Enable Command Summaries
        run: |
          echo "JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR=${{ runner.temp }}/jfrog-summary" >> $GITHUB_ENV
          echo "JFROG_URL=${{ secrets.JF_URL }}" >> $GITHUB_ENV
          mkdir -p "${{ runner.temp }}/jfrog-summary"

      - name: Build and Upload
        run: |
          jf rt upload "build/*.jar" my-repo/
          jf rt build-publish ${{ github.repository }} ${{ github.run_number }}

      - name: Security Scan
        run: jf build-scan ${{ github.repository }} ${{ github.run_number }}

      - name: Generate Summary
        run: jf gsm

      - name: Display Summary
        run: |
          echo "## JFrog CLI Summary" >> $GITHUB_STEP_SUMMARY
          cat "$JFROG_CLI_COMMAND_SUMMARY_OUTPUT_DIR/jfrog-command-summary/markdown.md" >> $GITHUB_STEP_SUMMARY

      - name: Upload Summary as Artifact
        uses: actions/upload-artifact@v4
        with:
          name: jfrog-summary
          path: ${{ runner.temp }}/jfrog-summary/

What’s Next

For the complete list of commands, see the JFrog CLI Command Reference.