diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index bc74005..855bb56 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -6,7 +6,7 @@ }, "metadata": { "description": "JFrog Platform plugins for Cursor", - "version": "0.5.8", + "version": "0.5.9", "pluginRoot": "plugins" }, "plugins": [ diff --git a/.github/scripts/sync-modules-vendor.json b/.github/scripts/sync-modules-vendor.json index 13ec90a..3f7cfad 100644 --- a/.github/scripts/sync-modules-vendor.json +++ b/.github/scripts/sync-modules-vendor.json @@ -1,6 +1,6 @@ { "repo": "JFROG/jfrog-agent-hooks", - "pin": "jfrog-agent-hooks/v0.6.0", + "pin": "jfrog-agent-hooks/v0.7.0", "paths": [ "modules" ] diff --git a/README.md b/README.md index f10d3df..ed92ce3 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ JFrog plugin for [Cursor](https://cursor.com): artifact management, security scanning, supply-chain best practices, and Agent Guard. -## What's new in v0.5.0 +## What's new -- **Official skills.** The plugin now uses the official [jfrog-skills](https://github.com/jfrog/jfrog-skills) v0.11.0, replacing the previously bundled skill content. This brings structured reference files, automation scripts, and a three-tier tool selection strategy (MCP, CLI, REST/GraphQL). -- **Package safety skill.** New `jfrog-package-safety-and-download` skill for checking whether packages are safe, curated, or allowed before downloading them through Artifactory. +- **Agent Package Resolution (Preview).** A new opt-in hook automatically routes the packages your AI agent installs through your JFrog Artifactory instead of public registries. See [Agent Package Resolution](#agent-package-resolution-preview). +- **AI Catalog skill.** New `jfrog-ai-catalog-skills` skill to discover, install, update, and publish agent skills hosted in the JFrog AI Catalog. --- ## Features @@ -16,6 +16,7 @@ The JFrog plugin provides the following capabilities, grouped by component: | --- | --- | --- | | **Skill** | JFrog Platform | Interact with Artifactory repositories, builds, permissions, users, access tokens, projects, release bundles, and platform administration via the JFrog CLI and REST/GraphQL APIs. Also covers security audits, CVE lookups, and Advanced Security exposure queries. | | **Skill** | Package safety & download | Check whether npm, Maven, PyPI, Go, and other packages are safe, curated, or allowed, then download them through Artifactory remote caches or curation-aware package managers. | +| **Hook + Skill** | Agent Package Resolution (Preview) | Automatically route packages installed by the AI agent through your organization's JFrog Artifactory, keeping agent-driven installs inside your Curation, Xray, and governance perimeter. | | **Hook** | Agent Guard | Cursor manage MCPs through the JFrog Agent Guard. Through the Agent Guard you can discover, install, configure, update, and remove MCP servers from the JFrog AI Catalog approved for your project, and authenticate to remote HTTP MCPs via OAuth, API key, or bearer token. | --- @@ -62,6 +63,19 @@ Run `jf login` for browser-based setup, or set the `JFROG_ACCESS_TOKEN` environm --- +## Agent Package Resolution (Preview) + +> **Preview Notice:** This feature is in preview and licensed under the Apache License 2.0. For clarity: This software is provided "as-is" without warranty of any kind, and without support obligations or service level commitments. Behavior, APIs, conventions, and structure may change without notice between releases. JFrog makes no guarantees of backward compatibility during the preview release cycle. Use in production environments is at your own risk. + +The plugin can now automatically route the packages your AI agent installs (npm, PyPI, Maven, Go, Docker, Helm, and NuGet) through your organization's JFrog Artifactory instead of public registries. This keeps agent-driven dependency installs inside your organization's governance perimeter. + +Agent Package Resolution is in preview and opt-in. To get started: + +- **Users:** see the [User Guide](docs/package-resolution-user-guide.md). +- **Admins:** see the [Admin Guide](docs/package-resolution-admin-guide.md). + +--- + ## Usage Once configured, interact with the JFrog plugin through natural language. Examples are grouped by capability. @@ -85,6 +99,16 @@ Once configured, interact with the JFrog plugin through natural language. Exampl | "Is this Maven package approved for use?" | Checks curation entitlement and policy for the requested package. | | "Download `requests` via JFrog." | Resolves the package through an Artifactory remote cache or curation-aware package manager. | +### Agent Package Resolution + +When Agent Package Resolution is enabled and configured, no special prompt syntax is required. Ask the agent to install or use a package as you normally would, and the plugin routes supported package operations through your organization's Artifactory. + +| Ask the agent… | What happens | +| -------------------------------------- | ------------------------------------------------------------------------ | +| "Add `lodash` to this project." | Resolves the npm package through the configured Artifactory repository. | +| "Add Excel file import to this app." | The agent selects a suitable package and resolves it through the configured Artifactory repository. | +| "Pull the `alpine` Docker image." | Pulls the image through the configured Artifactory Docker repository. | + ### MCP server management (Agent Guard) | Ask the agent… | What happens | diff --git a/docs/package-resolution-admin-guide.md b/docs/package-resolution-admin-guide.md new file mode 100644 index 0000000..4ea1ede --- /dev/null +++ b/docs/package-resolution-admin-guide.md @@ -0,0 +1,346 @@ +# Agent Package Resolution: Admin Guide (Preview) + +> **Preview Notice:** This feature is in preview and licensed under the Apache License 2.0. For clarity: This software is provided "as-is" without warranty of any kind, and without support obligations or service level commitments. Behavior, APIs, conventions, and structure may change without notice between releases. JFrog makes no guarantees of backward compatibility during the preview release cycle. Use in production environments is at your own risk. + +**Audience:** Artifactory administrators / DevOps teams rolling this out to users. + +Thank you for testing **Agent Package Resolution**, a feature in the JFrog plugin that routes AI coding-agent package installs (Cursor, Claude Code) through your organization's Artifactory instead of public registries, automatically and without relying on the person prompting the agent to know or follow your org's package-routing practices. That matters more now that coding agents let people beyond traditional developers write code and pull dependencies too. This guide covers everything an admin needs to turn it on and configure it for your organization during this preview. + +--- + +## Prerequisites + +The only hard prerequisites are the plugin and a JFrog Platform account for each user. Everything else below adapts automatically per user, rather than being something you need to arrange in advance: + +- **The JFrog plugin installed** in your users' coding assistant (Cursor and/or Claude Code), whether users install it themselves or your org distributes it centrally. +- **A JFrog Platform account for each user.** Whether their local environment is already set up to use it is not something you need to arrange; the feature detects and handles that itself, see [Operating modes](#operating-modes) below. + +--- + +## Setup summary + +Only two things are actually required from you as the admin: + + +| Step | Action | +| ---- | ------------------------------------------------------------------------------------------------------------------------- | +| 1 | Confirm the JFrog plugin is installed in your users' coding assistant | +| 2 | Enable Agent Package Resolution and set default repositories in `~/.jfrog/agents-conf.json`, for whichever users you want | + + +Everything else adapts on its own, per user, without you needing to arrange it (see [Prerequisites](#prerequisites) above). Once a user's `agents-conf.json` is enabled or changed, it takes effect automatically; there is no need to start a new agent session to pick up the change. + +Agent Package Resolution is **opt-in**, and enablement is per user, not global by default. You decide which users to enable it for: deploy or edit `agents-conf.json` centrally (MDM, golden image) for all or a chosen subset of machines, or have specific users edit their own file locally. For a preview, enabling it for a handful of users directly is often simpler than a full rollout. See "Configuration file" below. + +--- + +## Configuration file: `~/.jfrog/agents-conf.json` + +All Agent Package Resolution admin settings live in a single JSON file on each user's machine: + +``` +~/.jfrog/agents-conf.json +``` + + +| Property | Description | +| --------------------- | -------------------------------------------------------------------------------- | +| **Scope** | Per user profile (`$HOME`) | +| **Written by** | Administrators (MDM, golden image, manual edit) or auto-created on first session | +| **Read by** | JFrog plugin session hooks on every agent session start | +| **Never overwritten** | If the file already exists, the plugin does not replace it | + + +### First session behavior + +When a user opens their first agent session after installing the plugin: + +1. If `~/.jfrog/agents-conf.json` **does not exist**, the plugin copies the shipped default template into that path. +2. The template ships with Agent Package Resolution **disabled** (`packageResolution.enabled: false`). +3. No routing policy is injected until an administrator or user sets `packageResolution.enabled` to `true`. + +This lets you **pre-deploy** your own `agents-conf.json` (via MDM, Ansible, fleet policy, etc.) **before** users ever run the plugin. A pre-deployed file is never clobbered. + +### Shipped default template + +```json +{ + "logLevel": "info", + "packageResolution": { + "enabled": false, + "verifyRepos": true, + "cacheTtlDays": 7, + "defaultGlobalRepos": { + "npm": "npm-virtual", + "pypi": "pypi-virtual", + "maven": "maven-virtual", + "go": "go-virtual", + "docker": "docker-virtual", + "helm": "helm-virtual", + "nuget": "nuget-virtual" + }, + "autoSetup": [] + } +} +``` + +Replace the repository keys with the **repo keys that actually exist on your JFrog Platform**. The names above are examples only. To govern only some package types, list only those types in `defaultGlobalRepos`, see [Selective governance](#selective-governance-choose-which-package-types-to-route) below. + +--- + +## Selective governance: choose which package types to route + +You do not have to turn on every package type at once. The package types Agent Package Resolution actually routes for a session, the **governed** types, are the union of: + +- the keys you list in `defaultGlobalRepos` (your org default), and +- any keys declared in a project's workspace override file (see [Workspace-level repository overrides](#workspace-level-repository-overrides) below). + +Any package type you don't declare anywhere is **out of scope**: the agent installs it normally, with no routing, no friction, and no "unresolved" state to explain to your developers. This makes it easy to start narrow, for example just `npm` and `pypi`, and expand later, rather than committing to all 7 types on day one. See the "npm and PyPI only" example under [Configuration examples](#configuration-examples) below. + +--- + +## Rolling this out to multiple users at once + +If you're enabling this for more than a handful of users, use your standard endpoint management to push a consistent `agents-conf.json`, rather than editing each one by hand. This applies equally to a small preview group or a full org-wide rollout, it's the same file either way, just targeted at whichever machines you choose. + +**Typical rollout pattern:** + +1. Build a golden `agents-conf.json` for the users you're targeting (see the examples below). +2. Deploy it to `~/.jfrog/agents-conf.json` on their machines with your MDM or configuration management tool. That's it, the change takes effect automatically, no per-user auth check and no session restart needed (see [Prerequisites](#prerequisites)). + +**Tips for administrators** + + +| Goal | Approach | +| ------------------------------------------------------ | -------------------------------------------------------------------------------------- | +| Enable Agent Package Resolution for the targeted users | Set `"packageResolution": { "enabled": true, ... }` in the deployed file | +| Map to your Artifactory repos | Edit `defaultGlobalRepos` with your real repo keys | +| Force a refresh of the cached repo snapshot | Set `"cacheTtlDays": 0` (re-resolves repos every session; does **not** force autoSetup re-runs), or edit `agents-conf.json` (cache invalidates on file change) | +| Support troubleshooting | Set `"logLevel": "debug"` temporarily; logs go to `~/.jfrog/logs/agent-hooks.log` | + + +--- + +## Operating modes + +Once enablement is resolved, Agent Package Resolution runs in one of three modes each session: + + +| Mode | When | What the user sees | +| ----------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------- | +| **off** | `packageResolution.enabled` is not `true`, or the disable environment variable is set | Nothing; no Agent Package Resolution injection | +| **pending** | Enabled, but `jf` is missing or not configured | An advisory notice: routing is not ready, with setup steps | +| **routing** | Enabled and `jf` is configured and usable | Full routing policy with resolved Artifactory URLs | + + +`pending` is **advisory**: it steers the agent and user toward setup. It is not a hard block. Hard enforcement comes from Curation and bound package manager configuration. + +--- + +## Zero-touch setup: `autoSetup` + +By default, package-manager binding (`jf setup`) happens when the developer or agent runs it, typically via a one-time confirmation the first time a project uses a given package type. `autoSetup` lets you skip that confirmation: the plugin runs `jf setup` **automatically, in the background, at session start** for whichever governed package types you list. Binding usually finishes before the developer asks for anything, including indirect installs like a postinstall script, `pip install -r`, or `npx`. Because setup is asynchronous, a very early first install in the same session can still race it. + +```json +{ + "packageResolution": { + "enabled": true, + "defaultGlobalRepos": { + "npm": "npm-virtual", + "pypi": "pypi-virtual" + }, + "autoSetup": ["pypi"] + } +} +``` + +- `autoSetup` takes a list of package type names, or `true` to mean "all governed types." +- Only types that are both **governed** (declared in `defaultGlobalRepos` or a workspace override) and **resolved** are eligible; other names are ignored with a warning in the log. +- It only runs in `routing` mode (a working `jf` identity). Nothing is auto-configured in `pending` mode. +- It's off by default (`[]`) and safe to leave off: without it, setup still happens, just triggered by the developer's or agent's first use of that package type instead of automatically. +- It's idempotent. Each result is recorded in `~/.jfrog/skills-cache/package-setup.json`, keyed by server and package type, and trusted for `cacheTtlDays`. A repo that fails to configure (for example, a missing repo key or no permission) is deferred rather than retried every session. It retries when the TTL expires, or immediately if you change the repo key or JFrog server URL. + +--- + +## Configuration reference + +All keys are optional. Unknown keys are ignored. + +### Top level + + +| Key | Default | Description | +| ---------- | ------- | --------------------------------------------------------------------------------------------------------- | +| `logLevel` | `info` | Hook log verbosity: `silent`, `debug`, `info`, `warn`, `error`. Log file: `~/.jfrog/logs/agent-hooks.log` | + + +### `packageResolution` + + +| Key | Default | Description | +| -------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------- | +| `enabled` | `false` | `true` turns Agent Package Resolution on for the user (subject to `jf` / auth and the disable environment variable) | +| `verifyRepos` | `true` | When `true`, each repo key in `defaultGlobalRepos` is verified against Artifactory before use | +| `cacheTtlDays` | `7` | Days to reuse the verified repo snapshot **and** autoSetup receipts per JFrog server. For repo resolve, `0` re-resolves every session; for autoSetup receipts, `0` means no timer expiry (retry only on repo key / server URL change) | +| `defaultGlobalRepos` | See template above | Map of package type to Artifactory **repository key**. Keys also define which types are **governed**, see below | +| `autoSetup` | `[]` | Governed types to auto-configure with `jf setup` at session start, or `true` for all. See Zero-touch setup below | + + +**Supported package types:** `npm`, `pypi`, `maven`, `go`, `docker`, `helm`, `nuget`. + +--- + +Package types not listed in `defaultGlobalRepos` (and not declared in a workspace override) are **out of scope** for that session — the agent installs them normally, with no routing and no unresolved/setup flow, until you add the mapping. + +### Repo resolution order (per package type) + +1. **Workspace overlay:** `.jfrog/local/package-resolution.json` in the project (if present). Lets a team override the org default for a specific repo checkout. +2. **Cached snapshot:** `~/.jfrog/skills-cache/package-resolution.json` (per JFrog server, respects `cacheTtlDays`). +3. **Admin defaults:** `defaultGlobalRepos` in `agents-conf.json` (on cache miss or stale cache). + +## Configuration examples + +### Enable Agent Package Resolution with your repository keys + +```json +{ + "logLevel": "info", + "packageResolution": { + "enabled": true, + "verifyRepos": true, + "cacheTtlDays": 7, + "defaultGlobalRepos": { + "npm": "corp-npm-virtual", + "pypi": "corp-pypi-virtual", + "maven": "corp-maven-virtual", + "go": "corp-go-virtual", + "docker": "art-docker", + "helm": "corp-helm-local", + "nuget": "corp-nuget-virtual" + } + } +} +``` + +Deploy this file to `~/.jfrog/agents-conf.json` on users' machines. The change takes effect automatically, no restart needed. + +### npm and PyPI only (minimal preview rollout) + +A good way to start a preview without committing to all 7 package types at once: + +```json +{ + "packageResolution": { + "enabled": true, + "defaultGlobalRepos": { + "npm": "npm-virtual", + "pypi": "pypi-virtual" + } + } +} +``` + +Other package types stay out of scope until you expand the map. + +### Debug logging for support + +```json +{ + "logLevel": "debug", + "packageResolution": { + "enabled": true + } +} +``` + +Inspect `~/.jfrog/logs/agent-hooks.log` on the user's machine. Return to `"logLevel": "info"` after troubleshooting. + +--- + +## Emergency disable + +You can force Agent Package Resolution **off** for a process without editing `agents-conf.json`. Useful for CI images, break-glass support, or a temporary rollback during the preview. + + +| Variable | Value | Effect | +| ------------------------------------- | ----- | -------------------------------------------------------------------------------------------------------------------- | +| `JF_AGENT_PACKAGE_RESOLUTION_DISABLE` | `1` | Agent Package Resolution stays **off** for that IDE/terminal process, even if `agents-conf.json` has `enabled: true` | + + +**Precedence (enablement):** + +1. `JF_AGENT_PACKAGE_RESOLUTION_DISABLE=1` → **off** +2. `packageResolution.enabled: true` in `agents-conf.json` → **on** (if `jf` / auth allows) +3. Otherwise → **off** (shipped default) + +To set it: + +```bash +export JF_AGENT_PACKAGE_RESOLUTION_DISABLE=1 +``` + +Restart the coding assistant after changing environment variables. + +> Removing the variable (or setting it to anything other than `1`) restores file-based enablement from `agents-conf.json`. + +--- + +## Workspace-level repository overrides + +Users (or project templates) can override your org-wide defaults for a specific repository checkout: useful for mono-repos or team-specific repo keys, without touching the org-wide `agents-conf.json`. + +**File:** `/.jfrog/local/package-resolution.json` + +```json +{ + "repositories": { + "npm": "team-npm-virtual", + "pypi": "team-pypi-virtual" + } +} +``` + +Workspace values win over `agents-conf.json` for matching package types during that session. + +--- + +## Troubleshooting + + +| Symptom | What to check | +| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| No routing policy in the agent | `packageResolution.enabled` is `true` in `~/.jfrog/agents-conf.json` | +| Policy still off despite enabled config | `JF_AGENT_PACKAGE_RESOLUTION_DISABLE=1` is set in the IDE environment | +| Wrong repository URLs | Verify `defaultGlobalRepos` keys exist on your Platform; check `verifyRepos` and `~/.jfrog/skills-cache/package-resolution.json` | +| Invalid config ignored | Malformed JSON logs a **WARN** in `~/.jfrog/logs/agent-hooks.log` and falls back to safe defaults (`enabled: false`) | +| Reset to shipped defaults | Delete `~/.jfrog/agents-conf.json`; it is recopied automatically. Optionally delete `package-resolution.json` and `package-setup.json` from the cache to clear snapshots and setup receipts | +| `autoSetup` type not configured | Confirm the type is governed and the session was in `routing` mode. Check `agent-hooks.log` and `package-setup.json` in the skills cache | + + +--- + +## What this preview covers + +Agent Package Resolution runs at the start of every coding-agent session. When enabled, it injects routing policy and resolved Artifactory repository URLs into the session, so the agent prefers your repositories over public registries (npm, PyPI, Maven, Go, Docker, Helm, NuGet) for the rest of that session. + +**About this preview (please read before rolling out):** + +- **This is advisory steering, not a hard block.** The feature tells the agent which repository to use and nudges it to configure package managers accordingly. It does not intercept or rewrite the underlying install commands. If you need a hard guarantee that nothing reaches a public registry, that guarantee comes from the two mechanisms below, not from this session-injection layer alone. +- **Durable enforcement is `jf setup` (package manager configuration) plus server-side Curation.** Once a package manager is bound to your Artifactory repository (via `jf setup`, which the agent will run for you when needed), that binding persists across sessions and tools, independent of this feature. Curation policies on the server are what actually block disallowed packages. +- **All 7 package types are configurable** (npm, PyPI, Maven, Go, Docker, Helm, NuGet), but you do not have to turn them all on at once. A narrower starting scope (for example, just npm and PyPI) is a reasonable way to begin a preview rollout; see the configuration examples above. Package types you don't declare are left completely alone, see [Selective governance](#selective-governance-choose-which-package-types-to-route). +- **Package-manager binding can happen automatically** if you turn on `autoSetup` for a package type, instead of waiting for a developer's or agent's first use to trigger it. See [Zero-touch setup](#zero-touch-setup-autosetup). +- This is a **preview**. Expect rough edges, and please route feedback through the channel below. + +--- + +## Feedback and support + +This is a preview. Please report issues, confusion, or surprises. + +File an issue on GitHub, in whichever plugin repo you use: + +- Cursor: [github.com/jfrog/cursor-plugin/issues](https://github.com/jfrog/cursor-plugin/issues) +- Claude Code: [github.com/jfrog/claude-plugin/issues](https://github.com/jfrog/claude-plugin/issues) +- Email: plugins-feedback@jfrog.com + +We're especially interested in: whether the enablement steps above were clear, whether routing behaved as expected once enabled, and anything that felt broken or surprising during rollout. \ No newline at end of file diff --git a/docs/package-resolution-user-guide.md b/docs/package-resolution-user-guide.md new file mode 100644 index 0000000..9dfbf2c --- /dev/null +++ b/docs/package-resolution-user-guide.md @@ -0,0 +1,135 @@ +# Agent Package Resolution: User Guide (Preview) + +**Audience:** Users of Cursor or Claude Code with the JFrog plugin, whether or not you're a professional developer. + +You (or your org) installed the JFrog plugin. This is what happens next, step by step, when you ask your agent to do something that needs a package: install a dependency to build an app, pull a Docker image, and so on. + +--- + +## Prerequisite + +The JFrog plugin is installed. That's it; nothing else is required of you up front. + +## What will happen, by case + +You're mostly passive in all of this: the agent drives, and it tells you when it needs something from you. + +### CLI installed and authenticated + +- **You:** nothing. Just ask: + +> "Add lodash as a dependency" +> "Pull the alpine image and start a container" + +- **Agent:** routes your request through your organization's Artifactory right away. + +This is the state you'll be in almost all the time. + +### Server not configured + +- **Agent:** asks you for your JFrog Platform URL, then starts a login against it (`jf config add` / `jf login`). +- **You:** provide the URL and complete the login when prompted. + +### CLI not installed + +- **Agent:** installs the CLI. +- **You:** may need to approve the install (a normal IDE tool-permission prompt). + +### CLI not authenticated + +- **Agent:** launches a login (`jf login`, usually a browser session). +- **You:** complete the login when prompted. + +--- + +## Turning it on + +Agent Package Resolution is disabled by default; it needs to be enabled for any of the above to take effect. It can be turned on by your org, or by you directly. + +Edit `~/.jfrog/agents-conf.json` (created automatically the first time you use the plugin, so the file already exists) and set: + +```json +{ + "packageResolution": { + "enabled": true, + "defaultGlobalRepos": { + "npm": "npm-virtual", + "pypi": "pypi-virtual" + } + } +} +``` + +If a repository key isn't accurate for your org, update it to the correct one. If you don't know the correct key, or a key doesn't exist on your JFrog Platform, that package type simply stays unrouted until someone corrects it; nothing breaks. The change takes effect automatically, no need to start a new session. + +## Turning it off + +If routing is causing problems (wrong repository, broken installs, anything else), you can turn Agent Package Resolution off immediately without touching `agents-conf.json`: + +```bash +export JF_AGENT_PACKAGE_RESOLUTION_DISABLE=1 +``` + +Restart your IDE for it to take effect. This overrides `agents-conf.json`, so it works even if your org has enabled the feature centrally. Remove the variable (or restart without it set) to turn routing back on. Please also report the issue (see [Feedback](#feedback)) so we can fix it. + +--- + +## Good to know (doesn't require you to do anything) + +- **First time a project uses a given package type,** the agent may show a quick one-time confirmation ("apply this setup?") before it can route that package type. You just confirm; it's the agent doing setup work, not something you prepare for, and it won't ask again for that project. +- **Your admin may skip that confirmation entirely.** If they've turned on zero-touch setup for a package type, the plugin starts binding it to Artifactory automatically in the background when you start a session. You usually won't see a prompt for that package type. Because binding runs in the background, a very early first request in the same session can occasionally land before setup finishes. + +--- + +## Troubleshooting + +| Symptom | What to do | +|---------|-------------| +| Install fails with `401` / `403` even though routing looked ready | Your token is expired or revoked, not a repository problem; this isn't caught until an install actually fails. Log in again for that server | +| Nothing seems to be happening / no mention of Artifactory | It's off by default until someone turns it on; see [Turning it on](#turning-it-on) above to turn it on yourself, or check with your admin | +| Install used the wrong repository | Check whether your project has a `.jfrog/local/package-resolution.json` override, or ask your admin what the org default is for that package type. See [Advanced](#advanced-project-specific-repository-overrides) below | +| You want to temporarily turn this off | See [Turning it off](#turning-it-off) above | +| Something looks broken | Check `~/.jfrog/logs/agent-hooks.log` for details, and let us know (see below); this is exactly the kind of thing we want to hear about during the preview | + +--- + +## Feedback + +This is a preview, and your feedback directly shapes what ships next. Please tell us about anything that felt confusing, broken, or surprising, good or bad. + +File an issue on GitHub, in whichever plugin repo you use: + +- Cursor: [github.com/jfrog/cursor-plugin/issues](https://github.com/jfrog/cursor-plugin/issues) +- Claude Code: [github.com/jfrog/claude-plugin/issues](https://github.com/jfrog/claude-plugin/issues) +- Email: plugins-feedback@jfrog.com + +--- + +## Appendix: background and details + +### What is Agent Package Resolution, technically + +It's a feature in the JFrog plugin that runs at the start of every coding-agent session. When enabled, it checks routing readiness (as above) and, once ready, gives the agent the resolved Artifactory URL for each package type you use, so installs are routed there instead of the public registry, without changing your workflow. + +### About this preview (please read) + +- **This steers the agent, it does not hard-block installs.** The checks above happen because the agent is instructed to follow them, not because commands are intercepted or rewritten as you type them. If an install doesn't get routed the way you expect, that's useful feedback for us. +- **The real backstop is your package manager configuration plus Artifactory Curation**, which your admin sets up server-side. Once a package manager is bound to a repository for a project (the one-time setup step above), that binding is durable and persists across sessions, independent of this feature. +- This is a **preview**; you may hit rough edges. Please tell us about them. + +### Advanced: project-specific repository overrides + +If you're working in a repository that needs a different Artifactory repository than your org's default (for example, a team-specific mirror), you or your team can add a file to the project: + +**File:** `/.jfrog/local/package-resolution.json` + +```json +{ + "repositories": { + "npm": "team-npm-virtual", + "pypi": "team-pypi-virtual" + } +} +``` + +This overrides your org's default repository for the listed package types, for anyone working in that project. Most users won't need this; it's here for teams with special routing needs. It only changes which repository is used; it doesn't turn Agent Package Resolution on by itself, that still happens in `agents-conf.json` (see [Turning it on](#turning-it-on)). diff --git a/plugins/jfrog/.cursor-plugin/plugin.json b/plugins/jfrog/.cursor-plugin/plugin.json index 58538e0..af3a61f 100644 --- a/plugins/jfrog/.cursor-plugin/plugin.json +++ b/plugins/jfrog/.cursor-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "jfrog", "displayName": "JFrog Platform", - "version": "0.5.8", + "version": "0.5.9", "description": "JFrog Platform integration with MCP, security skills, Agent Package Resolution, supply-chain best practices, and JFrog Agent Guard governance for adding, removing, and listing MCP servers.", "author": { "name": "JFrog", diff --git a/plugins/jfrog/README.md b/plugins/jfrog/README.md index 88da7e4..29b0133 100644 --- a/plugins/jfrog/README.md +++ b/plugins/jfrog/README.md @@ -20,6 +20,7 @@ CLI authentication options: run `jf login` for browser-based setup, or set the ` |---|---|---| | **MCP** | `mcp.json` | Remote JFrog MCP server (OAuth, no API keys) | | **Hook** | `hooks/hooks.json` | Agent Guard — MCP server governance via JFrog AI Catalog | +| **Hook + Skill** | `hooks/hooks.json`, `skills/jfrog-setup-package-managers/` | Agent Package Resolution (Preview) — route agent package installs through Artifactory | ### Skills @@ -35,6 +36,17 @@ The **jfrog-ai-catalog-skills** skill (`skills/jfrog-ai-catalog-skills/`) discov The **jfrog-package-safety-and-download** skill (`skills/jfrog-package-safety-and-download/`) handles package safety checks — querying the JFrog Public Catalog, interpreting security signals, checking curation policies, and downloading packages through Artifactory remote caches. +## Agent Package Resolution (Preview) + +> **Preview Notice:** This feature is in preview and licensed under the Apache License 2.0. For clarity: This software is provided "as-is" without warranty of any kind, and without support obligations or service level commitments. Behavior, APIs, conventions, and structure may change without notice between releases. JFrog makes no guarantees of backward compatibility during the preview release cycle. Use in production environments is at your own risk. + +The plugin can automatically route the packages your AI agent installs (npm, PyPI, Maven, Go, Docker, Helm, and NuGet) through your organization's JFrog Artifactory instead of public registries. This keeps agent-driven dependency installs inside your organization's governance perimeter. + +Agent Package Resolution is in preview and opt-in. To get started: + +- **Users:** see the [User Guide](https://github.com/jfrog/cursor-plugin/blob/main/docs/package-resolution-user-guide.md). +- **Admins:** see the [Admin Guide](https://github.com/jfrog/cursor-plugin/blob/main/docs/package-resolution-admin-guide.md). + ## MCP Capabilities The JFrog MCP Server provides: diff --git a/plugins/jfrog/modules/assets/agents-default-conf.json b/plugins/jfrog/modules/assets/agents-default-conf.json index a721598..825351b 100644 --- a/plugins/jfrog/modules/assets/agents-default-conf.json +++ b/plugins/jfrog/modules/assets/agents-default-conf.json @@ -13,6 +13,6 @@ "helm": "helm-virtual", "nuget": "nuget-virtual" }, - "enforceOnStartup": [] + "autoSetup": [] } } diff --git a/plugins/jfrog/modules/core/agents-config.mjs b/plugins/jfrog/modules/core/agents-config.mjs index c9548ba..64a43df 100644 --- a/plugins/jfrog/modules/core/agents-config.mjs +++ b/plugins/jfrog/modules/core/agents-config.mjs @@ -164,7 +164,7 @@ export function loadAgentsConfig() { verifyRepos: pr.verifyRepos !== false, cacheTtlDays: normalizeCacheTtlDays(pr.cacheTtlDays), defaultGlobalRepos, - enforceOnStartup: normalizeEnforceOnStartup(pr.enforceOnStartup), + autoSetup: normalizeAutoSetup(pr.autoSetup), }, }; } @@ -184,15 +184,15 @@ export function globalDeclaredTypes() { } /** - * Repo-agnostic "enforce on startup" policy check for a single package type. - * `enforceOnStartup: true` means all governed types; an array names a subset. + * Repo-agnostic "auto setup" policy check for a single package type. + * `autoSetup: true` means all governed types; an array names a subset. * NOTE: this is a pure policy check — the caller still gates on the type being * governed + resolved this session. * @param {string} type * @returns {boolean} */ -export function isEnforceOnStartup(type) { - const e = loadAgentsConfig().packageResolution.enforceOnStartup; +export function isAutoSetup(type) { + const e = loadAgentsConfig().packageResolution.autoSetup; if (e === true) return true; return Array.isArray(e) && e.includes(type); } @@ -212,13 +212,13 @@ function normalizeCacheTtlDays(days) { } /** - * Normalize the `enforceOnStartup` policy: `true` (all governed types) or an + * Normalize the `autoSetup` policy: `true` (all governed types) or an * array of type-name strings. Anything else -> `[]` (nothing eager). Malformed * array entries (non-strings / blanks) are dropped; whether a named type is * actually governed is validated later (per-session, where governance is known). * @returns {true | string[]} */ -export function normalizeEnforceOnStartup(raw) { +export function normalizeAutoSetup(raw) { if (raw === true) return true; if (!Array.isArray(raw)) return []; const out = []; diff --git a/plugins/jfrog/modules/core/jf-identity.mjs b/plugins/jfrog/modules/core/jf-identity.mjs index b63a0a6..1ffa006 100644 --- a/plugins/jfrog/modules/core/jf-identity.mjs +++ b/plugins/jfrog/modules/core/jf-identity.mjs @@ -32,7 +32,7 @@ function normalizeUrl(u) { } // Resolution cause. `ok` means identity is present; the two failure causes -// drive cause-aware remediation in the enforce path: +// drive cause-aware remediation in the pending path: // jf-not-installed — `jf` is not on PATH / could not be executed. // jf-not-configured — `jf` ran but produced no usable server identity // (non-zero exit, empty/undecodable export, or a diff --git a/plugins/jfrog/modules/package-resolution/scripts/eager-setup.mjs b/plugins/jfrog/modules/package-resolution/scripts/eager-setup.mjs index a487396..a180f29 100644 --- a/plugins/jfrog/modules/package-resolution/scripts/eager-setup.mjs +++ b/plugins/jfrog/modules/package-resolution/scripts/eager-setup.mjs @@ -1,8 +1,8 @@ -// Eager `jf setup` — "enforce on startup". +// Eager `jf setup` — "auto setup on startup". // // Two roles in one file: // 1. ORCHESTRATOR (foreground, imported by index.mjs): after resolution, -// figure out which governed + `enforceOnStartup` + resolved types still +// figure out which governed + `autoSetup` + resolved types still // need `jf setup` (per the receipt), spawn a DETACHED background worker for // them, and return a short status note for the injected instruction. Never // runs `jf setup` itself — injection must stay fast (< 7s hook budget). @@ -33,7 +33,7 @@ import { fileURLToPath } from "node:url"; import { createLogger } from "../../core/logger.mjs"; import { loadAgentsConfig, - isEnforceOnStartup, + isAutoSetup, } from "../../core/agents-config.mjs"; import { getPlatformIdentity } from "../../core/jf-identity.mjs"; import { @@ -50,10 +50,10 @@ import { const log = createLogger("eager-setup"); -/** Actionable hint when enforceOnStartup names a type that isn't governed. */ -function ungovernedEnforceHint(type) { +/** Actionable hint when autoSetup names a type that isn't governed. */ +function ungovernedAutoSetupHint(type) { return ( - `trying to eager-configure '${type}' via enforceOnStartup but it is not ` + + `trying to eager-configure '${type}' via autoSetup but it is not ` + "governed — no repo found in defaultGlobalRepos " + "(~/.jfrog/agents-conf.json) or repositories in " + ".jfrog/local/package-resolution.json" @@ -92,8 +92,8 @@ function workerPath() { // --------------------------------------------------------------------------- /** - * Compute eligible eager-setup jobs = governed ∩ resolved ∩ enforceOnStartup. - * Warns when `enforceOnStartup` names an ungoverned type (ignored, not fatal). + * Compute eligible eager-setup jobs = governed ∩ resolved ∩ autoSetup. + * Warns when `autoSetup` names an ungoverned type (ignored, not fatal). * @param {string[]} governed * @param {Record} resolvedByType * @returns {{type:string, repoKey:string, pm:string}[]} @@ -102,10 +102,10 @@ export function computeEligibleJobs(governed, resolvedByType) { const governedSet = new Set(governed); const jobs = []; for (const type of governed) { - if (!isEnforceOnStartup(type)) continue; + if (!isAutoSetup(type)) continue; const r = resolvedByType[type]; if (!r) { - log.debug("eager skip: enforced but unresolved", { type }); + log.debug("eager skip: auto-setup but unresolved", { type }); continue; } const pm = TYPE_TO_PM[type]; @@ -115,12 +115,12 @@ export function computeEligibleJobs(governed, resolvedByType) { } jobs.push({ type, repoKey: r.repoKey, pm }); } - // Surface admin misconfig: enforceOnStartup naming a type that isn't governed. - const { enforceOnStartup } = loadAgentsConfig().packageResolution; - if (Array.isArray(enforceOnStartup)) { - for (const type of enforceOnStartup) { + // Surface admin misconfig: autoSetup naming a type that isn't governed. + const { autoSetup } = loadAgentsConfig().packageResolution; + if (Array.isArray(autoSetup)) { + for (const type of autoSetup) { if (!governedSet.has(type)) { - log.warn(`eager setup skipped: ${ungovernedEnforceHint(type)}`, { + log.warn(`eager setup skipped: ${ungovernedAutoSetupHint(type)}`, { type, }); } @@ -176,8 +176,8 @@ function spawnWorker(payloadB64) { } /** - * Foreground entry called from sessionStart (active mode only). Decides which - * governed+enforced+resolved types need `jf setup`, spawns the background worker + * Foreground entry called from sessionStart (routing mode only). Decides which + * governed+auto-setup+resolved types need `jf setup`, spawns the background worker * if any do, and returns a status note for the injected instruction ("" if * nothing to say). Never throws — eager setup must never break injection. * @param {{ workspaceRoots?: string[] }} ctx diff --git a/plugins/jfrog/modules/package-resolution/scripts/feature-flag.mjs b/plugins/jfrog/modules/package-resolution/scripts/feature-flag.mjs index 9518969..f1c6701 100644 --- a/plugins/jfrog/modules/package-resolution/scripts/feature-flag.mjs +++ b/plugins/jfrog/modules/package-resolution/scripts/feature-flag.mjs @@ -6,15 +6,15 @@ // 1. JF_AGENT_PACKAGE_RESOLUTION_DISABLE=1 → mode="off" (env kill switch) // 2. packageResolution.enabled !== true in → mode="off" (file-primary gate; // ~/.jfrog/agents-conf.json default off in shipped template) -// 3. jf config (via jf-identity) → mode="active" when identity is -// usable; otherwise mode="enforce" with a `cause` (jf-not-installed / +// 3. jf config (via jf-identity) → mode="routing" when identity is +// usable; otherwise mode="pending" with a `cause` (jf-not-installed / // jf-not-configured) so the session hook can inject a targeted, // remediation-focused advisory notice. // // Modes: // "off" — do nothing (no injection). -// "active" — inject resolved Artifactory URLs + routing policy. -// "enforce" — jf missing/unconfigured: inject the advisory "routing not +// "routing" — inject resolved Artifactory URLs + routing policy. +// "pending" — jf missing/unconfigured: inject the advisory "routing not // ready" notice (no resolved URLs). This is advisory steering, // not a hard block — real enforcement is durable PM config // (jf setup) + server-side Curation. @@ -51,13 +51,13 @@ export async function isPackageResolutionEnabled() { const { identity, cause } = getPlatformIdentity(); if (!identity) { - log.debug("enforce", { reason: "missing-identity", cause }); - return { mode: "enforce", reason: "missing-identity", identity: "none", cause }; + log.debug("pending", { reason: "missing-identity", cause }); + return { mode: "pending", reason: "missing-identity", identity: "none", cause }; } - log.debug("active", { reason: "jf-config", identity: identityLabel(identity) }); + log.debug("routing", { reason: "jf-config", identity: identityLabel(identity) }); return { - mode: "active", + mode: "routing", reason: "jf-config", identity: identityLabel(identity), cause: "ok", diff --git a/plugins/jfrog/modules/package-resolution/scripts/index.mjs b/plugins/jfrog/modules/package-resolution/scripts/index.mjs index d6a9a94..8551012 100644 --- a/plugins/jfrog/modules/package-resolution/scripts/index.mjs +++ b/plugins/jfrog/modules/package-resolution/scripts/index.mjs @@ -10,7 +10,7 @@ import { orchestrateEagerSetup } from "./eager-setup.mjs"; export const packageResolution = { name: "package-resolution", - // Last resolved feature-flag mode ("off"|"enforce"|"active") and render detail + // Last resolved feature-flag mode ("off"|"pending"|"routing") and render detail // for the dispatcher EVENT log line. mode: undefined, meta: undefined, @@ -20,22 +20,23 @@ export const packageResolution = { const flag = await isPackageResolutionEnabled(); this.mode = flag.mode; - // Feature 2 — enforce on startup. Only in active mode (identity + resolution - // available). Runs OFF the critical path: it just decides what needs setup, - // spawns a detached worker, and returns a note. Never blocks/breaks injection. - let enforceStatus = ""; - if (flag.mode === "active") { - enforceStatus = await orchestrateEagerSetup(ctx); + // Feature 2 — auto setup on startup. Only in routing mode (identity + + // resolution available). Runs OFF the critical path: it just decides what + // needs setup, spawns a detached worker, and returns a note. Never + // blocks/breaks injection. + let autoSetupStatus = ""; + if (flag.mode === "routing") { + autoSetupStatus = await orchestrateEagerSetup(ctx); } const { text, meta } = await renderInstruction(flag, { ...ctx, - enforceStatus, + autoSetupStatus, }); this.meta = { reason: flag.reason, identity: flag.identity ?? "-", - ...(enforceStatus ? { eagerSetup: true } : {}), + ...(autoSetupStatus ? { eagerSetup: true } : {}), ...meta, }; return text; diff --git a/plugins/jfrog/modules/package-resolution/scripts/print-policy.mjs b/plugins/jfrog/modules/package-resolution/scripts/print-policy.mjs index de70da4..09f071a 100644 --- a/plugins/jfrog/modules/package-resolution/scripts/print-policy.mjs +++ b/plugins/jfrog/modules/package-resolution/scripts/print-policy.mjs @@ -2,14 +2,14 @@ // On-demand package-resolution policy printer. // // Unlike modules/*-session-start.mjs, this is NOT wired to a hook event. It is -// invoked manually (by the agent, per the enforce notice) so a session that +// invoked manually (by the agent, per the pending notice) so a session that // started "unconfigured" can load the up-to-date routing policy — resolved // Artifactory URLs + hard rules — on demand once `jf` is configured. // // It delegates to the exact same `packageResolution.sessionStart(ctx)` the // session-start hook runs, so recovery behaves identically to opening a fresh // session: it warms ~/.jfrog/skills-cache/package-resolution.json AND triggers -// eager `jf setup` (background worker + receipt + lock) for enforced types. +// eager `jf setup` (background worker + receipt + lock) for auto-setup types. // Safe to run repeatedly — the receipt/lock dedupe. print-policy is agent-invoked // (not the 7s hook), so the background spawn is fine. // diff --git a/plugins/jfrog/modules/package-resolution/scripts/render-instruction.mjs b/plugins/jfrog/modules/package-resolution/scripts/render-instruction.mjs index 8333fcc..2819037 100644 --- a/plugins/jfrog/modules/package-resolution/scripts/render-instruction.mjs +++ b/plugins/jfrog/modules/package-resolution/scripts/render-instruction.mjs @@ -5,8 +5,8 @@ // IDE-specific shaping) so every per-harness adapter can reuse it. // // mode "off" → "" (nothing to inject) -// mode "enforce" → the advisory "routing not ready" notice -// mode "active" → the routing policy with resolved Artifactory URLs +// mode "pending" → the advisory "routing not ready" notice +// mode "routing" → the routing policy with resolved Artifactory URLs import { readFile } from "node:fs/promises"; import path from "node:path"; @@ -26,8 +26,8 @@ const log = createLogger("render-instruction"); const here = path.dirname(fileURLToPath(import.meta.url)); const TEMPLATES_DIR = path.join(here, "../templates"); -const ACTIVE_TEMPLATE = "package-resolution.md"; -const ENFORCE_TEMPLATE = "package-resolution-unconfigured.md"; +const ROUTING_TEMPLATE = "package-resolution.md"; +const PENDING_TEMPLATE = "package-resolution-unconfigured.md"; // Command the agent runs after configuring `jf` to load routing in the SAME // session (no restart). Absolute path so it works regardless of the agent's cwd @@ -36,7 +36,7 @@ function refreshCommand() { return `node "${path.join(here, "print-policy.mjs")}"`; } -// Prose fragment for the enforce-notice {{CAUSE_REMEDIATION}} placeholder. +// Prose fragment for the pending-notice {{CAUSE_REMEDIATION}} placeholder. function causeRemediation(cause) { if (cause === "jf-not-installed") { return ( @@ -144,10 +144,10 @@ function buildDockerSection(governed, resolved) { return "\n## Docker (before any `docker pull`)\n\n" + body + "\n"; } -// Enforce-mode scope line — the governed PMs are known from config alone (no +// Pending-mode scope line — the governed PMs are known from config alone (no // network / no resolution needed). Notes that matching PMs will be // auto-configured once routing is ready. Does NOT claim any type is routed yet. -function buildEnforceGovernedScope() { +function buildPendingGovernedScope() { const governed = globalDeclaredTypes(); if (!governed.length) { return ( @@ -185,7 +185,7 @@ function buildGovernedScope(governed) { * into its single "sessionStart injected" EVENT line so the default-level log * stays one line but still carries the detail the POC printed. * - * @param {{ mode: "off"|"enforce"|"active", cause?: string }} flag + * @param {{ mode: "off"|"pending"|"routing", cause?: string }} flag * @param {{ workspaceRoots?: string[] }} [ctx] * @returns {Promise<{ text: string, meta: object }>} text is "" when there is * nothing to inject. @@ -193,9 +193,9 @@ function buildGovernedScope(governed) { export async function renderInstruction(flag, ctx = {}) { if (!flag || flag.mode === "off") return { text: "", meta: { mode: "off" } }; - if (flag.mode === "enforce") { + if (flag.mode === "pending") { let notice = await readFile( - path.join(TEMPLATES_DIR, ENFORCE_TEMPLATE), + path.join(TEMPLATES_DIR, PENDING_TEMPLATE), "utf8", ); notice = notice.replace( @@ -209,22 +209,22 @@ export async function renderInstruction(flag, ctx = {}) { notice = notice.replace(/\{\{REFRESH_COMMAND\}\}/g, refreshCommand()); notice = notice.replace( /\{\{GOVERNED_SCOPE\}\}/g, - buildEnforceGovernedScope(), + buildPendingGovernedScope(), ); // Detail line — kept at debug so the default level shows a single EVENT per // session (the dispatcher's "sessionStart injected"). Raise the level to see // the cause/byte breakdown. - log.debug("enforce notice rendered", { + log.debug("pending notice rendered", { cause: flag.cause, bytes: notice.length, }); return { text: notice, - meta: { cause: flag.cause, template: ENFORCE_TEMPLATE }, + meta: { cause: flag.cause, template: PENDING_TEMPLATE }, }; } - // active: resolve only the GOVERNED types (admin defaultGlobalRepos keys UNION + // routing: resolve only the GOVERNED types (admin defaultGlobalRepos keys UNION // workspace-declared keys) and build the table / bullets / docker section // dynamically so ungoverned types disappear entirely (not blocked). await prepareSessionResolve({ workspaceRoots: ctx.workspaceRoots }); @@ -238,7 +238,7 @@ export async function renderInstruction(flag, ctx = {}) { } let template = await readFile( - path.join(TEMPLATES_DIR, ACTIVE_TEMPLATE), + path.join(TEMPLATES_DIR, ROUTING_TEMPLATE), "utf8", ); template = template @@ -250,8 +250,8 @@ export async function renderInstruction(flag, ctx = {}) { ) .replace(/\{\{DOCKER_SECTION\}\}/g, buildDockerSection(governed, resolved)) .replace( - /\{\{ENFORCE_STATUS\}\}/g, - ctx.enforceStatus ? `\n${ctx.enforceStatus}\n` : "", + /\{\{AUTO_SETUP_STATUS\}\}/g, + ctx.autoSetupStatus ? `\n${ctx.autoSetupStatus}\n` : "", ); const resolvedCompact = @@ -261,9 +261,9 @@ export async function renderInstruction(flag, ctx = {}) { const unresolvedCompact = unresolved.join(",") || "-"; const rm = getResolveSessionMeta(); - // Detail line — kept at debug (see the enforce branch above) so the default + // Detail line — kept at debug (see the pending branch above) so the default // level shows a single EVENT per session. - log.debug("active instruction rendered", { + log.debug("routing instruction rendered", { governed: governed.join(",") || "-", resolved: resolvedCompact, unresolved: unresolvedCompact, @@ -280,7 +280,7 @@ export async function renderInstruction(flag, ctx = {}) { governed: governed.join(",") || "-", resolved: resolvedCompact, unresolved: unresolvedCompact, - template: ACTIVE_TEMPLATE, + template: ROUTING_TEMPLATE, }; // Workspace fields only when a local file was read and applied to resolution. diff --git a/plugins/jfrog/modules/package-resolution/templates/package-resolution.md b/plugins/jfrog/modules/package-resolution/templates/package-resolution.md index 3b3f438..b62842c 100644 --- a/plugins/jfrog/modules/package-resolution/templates/package-resolution.md +++ b/plugins/jfrog/modules/package-resolution/templates/package-resolution.md @@ -5,7 +5,7 @@ Your organization mediates package fetches through JFrog Artifactory for the shell, sub-agent, or MCP tool — route through the resolved Artifactory repository. {{GOVERNED_SCOPE}} -{{ENFORCE_STATUS}} +{{AUTO_SETUP_STATUS}} ## Resolved URLs for this session {{RESOLVED_TABLE}}