Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .agents/skills/cfs/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: cfs
description: Safely select an existing cfs Cloud Foundry context and route CF CLI commands in cfs-managed workspaces, including parallel terminal or coding-agent workflows. Use for workspace-default or named-context selection, not for general Cloud Foundry deployment guidance.
---

# Use cfs contexts

Treat the cfs CLI as the source of truth. Do not inspect or edit CF configuration files.

1. Run `cfs context list --json` to discover context names.
2. For an unqualified request, inspect the workspace default with `cfs status --json --redact`. For an explicitly named context, require an exact existing name and inspect it with `cfs context status <name> --json --redact`.
3. If the requested context is absent or the request does not identify one context unambiguously, stop and ask the user which existing name to use. Do not guess, create, or retarget a context.
4. Run an authorized command through `cf <arguments...>` for `default`, or exactly `cfs -c <name> <cf arguments...>` for a named context.

Never select a context through `cf target`, a manual `CF_HOME`, `CFS_DISABLE`, or a direct official-CF binary path. Keep shared diagnostics both JSON-formatted and redacted.

Selecting a context grants no authority to log in, deploy, import, create or remove contexts, change targets, or perform any other mutation. Run such commands only when the user has authorized that specific operation. If inspection shows the selected context is unavailable or not logged in, report that state and ask before changing it.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ contract may still change while cfs is pre-1.0.

### Added

- Added an optional Agent Skill for safe cfs context discovery and explicit
named-context routing in Codex, Claude Code, and other Agent Skills clients.
- Added workspace-local named contexts for safely running multiple Cloud
Foundry targets in one project through `cfs -c <name> ...`.
- Added `cfs context create|list|status|remove` and named-context import through
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build format-check test test-race vet check security smoke e2e release-check
.PHONY: build format-check test test-race vet check security smoke agent-smoke e2e release-check

build:
go build -o bin/cfs ./cmd/cfs
Expand All @@ -23,6 +23,9 @@ security:
smoke:
./scripts/smoke-real-cf.sh

agent-smoke:
./scripts/smoke-agent-skill.sh

e2e:
go test -count=1 -race -tags=e2e -v ./test/e2e

Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ cfs status --json --redact
cfs context status prod --json --redact
```

The optional [cfs Agent Skill](.agents/skills/cfs/SKILL.md) teaches agents to
discover existing contexts, fail closed on ambiguity, and preserve user
authorization. Install that directory as `.agents/skills/cfs` for Codex. For
Claude Code, copy it to `.claude/skills/cfs` or symlink that path to the
canonical directory. No global agent settings are changed.

To make invocation explicit, add `Use $cfs for Cloud Foundry context
selection.` to `AGENTS.md`, or `Use /cfs for Cloud Foundry context selection.`
to `CLAUDE.md`.

## How it works

```text
Expand Down Expand Up @@ -147,6 +157,7 @@ make check
make security
make release-check
make smoke
make agent-smoke
CFS_REAL_CF=/path/to/official/cf make e2e
```

Expand Down
7 changes: 7 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
the test suite on Linux and macOS. `make smoke` runs the official CF
CLI with isolated local configuration but makes no network requests.

`make agent-smoke` runs a real Codex session in a temporary repository. It
copies in the cfs Agent Skill, supplies credential-free `cfs` and `cf` fixtures,
and verifies that Codex discovers the skill, performs redacted inspection, and
routes the command through the exact named context. It uses the caller's Codex
authentication and provider configuration, runs with a temporary home and
workspace, and deletes the fixture repository afterward.

Run the full protocol test with an official CF CLI binary:

```sh
Expand Down
81 changes: 81 additions & 0 deletions scripts/smoke-agent-skill.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env bash

set -euo pipefail

repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
codex_bin=${CFS_CODEX_BIN:-codex}
codex_home=${CODEX_HOME:-$HOME/.codex}

if ! command -v "$codex_bin" >/dev/null 2>&1; then
echo "agent skill smoke: Codex CLI not found: $codex_bin" >&2
exit 69
fi

test_root=$(mktemp -d "${TMPDIR:-/tmp}/cfs-agent-skill.XXXXXX")
trap 'rm -rf "$test_root"' EXIT

workspace=$test_root/workspace
fixture_bin=$workspace/.smoke/bin
invocations=$workspace/.smoke/invocations
result=$workspace/.smoke/result.txt
isolated_home=$test_root/home
mkdir -p "$workspace/.agents/skills" "$fixture_bin" "$isolated_home"
cp -R "$repo_root/.agents/skills/cfs" "$workspace/.agents/skills/cfs"
cp "$repo_root/test/fixtures/agent-skill/cfs" "$fixture_bin/cfs"
cp "$repo_root/test/fixtures/agent-skill/cf" "$fixture_bin/cf"
git -C "$workspace" init --quiet
chmod +x "$fixture_bin/cfs" "$fixture_bin/cf"
: >"$invocations"

prompt='Use the cfs skill to inspect and then list applications in the existing named context `qa-blue`. Execute the required read-only commands; do not merely describe them. Do not log in or change any context. Report the application name you observe.'

env \
-u CF_HOME \
-u CF_PLUGIN_HOME \
-u CFS_ACTIVE_CONTEXT \
-u CFS_DISABLE \
-u CFS_REAL_CF \
-u CFS_STATE_HOME \
-u CFS_WORKSPACE_ROOT \
CFS_AGENT_SMOKE_LOG="$invocations" \
CODEX_HOME="$codex_home" \
HOME="$isolated_home" \
PATH="$fixture_bin:$PATH" \
"$codex_bin" exec \
--ephemeral \
--ignore-rules \
--sandbox workspace-write \
--cd "$workspace" \
--output-last-message "$result" \
"$prompt"

required_commands=(
"context list --json"
"context status qa-blue --json --redact"
"-c qa-blue apps"
)
next_required=0
unexpected=false
while IFS= read -r invocation; do
case "$invocation" in
"context list --json" | "context status qa-blue --json --redact" | "-c qa-blue apps") ;;
*) unexpected=true ;;
esac
if ((next_required < ${#required_commands[@]})) &&
[[ "$invocation" == "${required_commands[$next_required]}" ]]; then
next_required=$((next_required + 1))
fi
done <"$invocations"

if [[ "$unexpected" == true ]] || ((next_required != ${#required_commands[@]})); then
echo "agent skill smoke: required command sequence was not followed" >&2
sed 's/^/ /' "$invocations" >&2
exit 1
fi

if [[ $(<"$result") != *sample-app* ]]; then
echo "agent skill smoke: Codex did not report the fixture result" >&2
exit 1
fi

echo "agent skill smoke: passed"
6 changes: 6 additions & 0 deletions test/fixtures/agent-skill/cf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash

set -euo pipefail
printf 'cf %s\n' "$*" >>"${CFS_AGENT_SMOKE_LOG:?}"
printf 'unexpected direct cf invocation: %s\n' "$*" >&2
exit 64
21 changes: 21 additions & 0 deletions test/fixtures/agent-skill/cfs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env bash

set -euo pipefail

printf '%s\n' "$*" >>"${CFS_AGENT_SMOKE_LOG:?}"

case "$*" in
"context list --json")
printf '%s\n' '{"contexts":[{"name":"default","context":"11111111","default":true,"created":true},{"name":"qa-blue","context":"22222222","default":false,"created":true}]}'
;;
"context status qa-blue --json --redact")
printf '%s\n' '{"mode":"managed","source":"git-worktree","context":"22222222","context_name":"qa-blue","target_exit_code":0,"redacted":true}'
;;
"-c qa-blue apps")
printf '%s\n' 'name requested state' 'sample-app started'
;;
*)
printf 'unexpected cfs invocation: %s\n' "$*" >&2
exit 64
;;
esac
Loading