Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
e20b1f6
commit copy env
burtenshaw Nov 1, 2025
2ebce29
fix yaml on branch
burtenshaw Nov 1, 2025
92501b5
reduce wait to 3 minutes
burtenshaw Nov 1, 2025
37cd0ac
add commenting to space pr
burtenshaw Nov 1, 2025
e2249f3
Merge branch 'main' into testing-pr-ci
burtenshaw Nov 1, 2025
e0ba987
rename
burtenshaw Nov 1, 2025
69f6556
Add OpenEnv CLI for managing environments on HuggingFace Spaces
zkwentz Nov 2, 2025
1a7a601
Update openspiel to reference BASE_IMAGE to support cli base-image ov…
zkwentz Nov 2, 2025
7624f24
Refactor push command to handle dry run more cleanly
zkwentz Nov 2, 2025
4904e67
Enhance README preparation logic in builder module
zkwentz Nov 2, 2025
a326c9d
Update color schemes to HF approved colors
zkwentz Nov 2, 2025
d9b4b14
Add space name support in push command and repository ID retrieval
zkwentz Nov 2, 2025
3c1c628
Update README documentation for `openenv push` command
zkwentz Nov 2, 2025
2b0741d
Remove outdated Phase 2 integration reference from README.md
zkwentz Nov 2, 2025
94a94a2
locg with rich in main
burtenshaw Nov 2, 2025
690cfdf
drop print statements in logic
burtenshaw Nov 2, 2025
5e58273
add rich dependency
burtenshaw Nov 2, 2025
e9e334f
Merge pull request #135 from meta-pytorch/openenv-cli/v1-ben
zkwentz Nov 2, 2025
e873c36
Fix nits from Github Copilot
zkwentz Nov 2, 2025
fc9ec5c
Fixed in-test imports in builder and no longer needed tests for merge…
zkwentz Nov 2, 2025
6e118e7
Enhance README generation in builder.py to handle empty environment n…
zkwentz Nov 2, 2025
475ca81
Refactor space creation error handling in create_space function
zkwentz Nov 2, 2025
0064c1c
Update README.md
zkwentz Nov 2, 2025
d30f42b
🤗
zkwentz Nov 3, 2025
2131e41
Better devex by supporting interactive login only (no $HF_TOKEN)
zkwentz Nov 3, 2025
880ec6d
Moved openenv cli usage instructions to main README
zkwentz Nov 3, 2025
98d7b0a
Refactor `openenv push` command to use `--repo-id` for specifying Hug…
zkwentz Nov 3, 2025
02a4174
Refactor `push` command to improve environment preparation and upload…
zkwentz Nov 3, 2025
3c0c432
Add openenv_cli package and update build workflow
zkwentz Nov 3, 2025
9756cf9
All user interaction happens in __main__ following HF hub approach
zkwentz Nov 3, 2025
6d1ee8d
Fixing nits, various refactors
zkwentz Nov 3, 2025
50240d3
Remove outdated authentication section from README.md
zkwentz Nov 3, 2025
37db23f
Add comprehensive tests for CLI utilities and environment loader
zkwentz Nov 3, 2025
4298bd6
use cli in action and update comment
burtenshaw Nov 3, 2025
a1a1b47
add delete space step
burtenshaw Nov 3, 2025
1a76128
streamline comment
burtenshaw Nov 3, 2025
284ffdc
Merge pull request #4 from burtenshaw/openenv-cli/v1-ci
burtenshaw Nov 3, 2025
7e77e72
test change
burtenshaw Nov 3, 2025
541d0a2
Merge branch 'main' into testing-pr-ci
burtenshaw Nov 3, 2025
b8b6126
hotfix coloring in readme
burtenshaw Nov 3, 2025
d826161
share github action url in comment
burtenshaw Nov 3, 2025
61857bf
test commit
burtenshaw Nov 3, 2025
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
118 changes: 97 additions & 21 deletions .github/workflows/pr-new-env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ jobs:
environment: ${{ fromJSON(needs.detect-new-envs.outputs.new_envs_json) }}
env:
HF_TOKEN: ${{ secrets.HF_PR_TOKEN }}
HUGGINGFACEHUB_API_TOKEN: ${{ secrets.HF_PR_TOKEN }}
HF_NAMESPACE: ${{ vars.HF_PR_NAMESPACE }}
SPACE_SUFFIX: -pr-${{ github.event.number }}
steps:
Expand All @@ -127,18 +128,24 @@ jobs:
exit 1
fi

- name: Install Hugging Face CLI
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install OpenEnv package
shell: bash
run: |
curl -LsSf https://hf.co/cli/install.sh | bash
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
set -euo pipefail
python -m pip install --upgrade pip
pip install .

- name: Deploy environment to Hugging Face
- name: Deploy environment with OpenEnv CLI
shell: bash
run: |
set -euo pipefail
chmod +x scripts/deploy_to_hf.sh
./scripts/deploy_to_hf.sh --env "${{ matrix.environment }}" --space-suffix "${SPACE_SUFFIX}" --hub-tag "openenv-pr"
repo_id="${HF_NAMESPACE}/${{ matrix.environment }}${SPACE_SUFFIX}"
openenv push "${{ matrix.environment }}" --repo-id "$repo_id"

- name: Wait for deployment to stabilize
shell: bash
Expand All @@ -161,13 +168,15 @@ jobs:
health_url="https://${namespace_slug}-${space_slug}.hf.space/health"
live_url="https://${namespace_slug}-${space_slug}.hf.space"
space_repo_url="https://huggingface.co/spaces/${HF_NAMESPACE}/${space_name}"
space_repo_id="${HF_NAMESPACE}/${space_name}"

echo "namespace_slug=${namespace_slug}" >> "$GITHUB_OUTPUT"
echo "space_name=${space_name}" >> "$GITHUB_OUTPUT"
echo "space_slug=${space_slug}" >> "$GITHUB_OUTPUT"
echo "health_url=${health_url}" >> "$GITHUB_OUTPUT"
echo "live_url=${live_url}" >> "$GITHUB_OUTPUT"
echo "space_repo_url=${space_repo_url}" >> "$GITHUB_OUTPUT"
echo "space_repo_id=${space_repo_id}" >> "$GITHUB_OUTPUT"

- name: Perform environment health check
id: health_check
Expand Down Expand Up @@ -216,41 +225,108 @@ jobs:
SPACE_NAME: ${{ steps.urls.outputs.space_name }}
LIVE_URL: ${{ steps.urls.outputs.live_url }}
SPACE_REPO_URL: ${{ steps.urls.outputs.space_repo_url }}
SPACE_REPO_ID: ${{ steps.urls.outputs.space_repo_id }}
ENV_NAME: ${{ matrix.environment }}
COMMENT_TAG: "<!-- openenv-pr-preview -->"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const status = process.env.HEALTH_CONCLUSION || 'failure';
const spaceName = process.env.SPACE_NAME;
const liveUrl = process.env.LIVE_URL;
const repoUrl = process.env.SPACE_REPO_URL;
const repoId = process.env.SPACE_REPO_ID;
const envName = process.env.ENV_NAME;
const marker = process.env.COMMENT_TAG;

const header = status === 'success'
? `✅ Deployment succeeded for \`${envName}\``
: `⚠️ Deployment failed for \`${envName}\``;
? `✅ Deployment to Hugging Face succeeded for \`${envName}\``
: `⚠️ Deployment Hugging Face failed for \`${envName}\``;

const summary = status === 'success'
? 'Nice work! Wait for a code review and we\'re ready to go.'
: 'Please resolve your environment.';
? 'Nice work! Wait for a code review and we\'re ready to go. You can test it with the CLI:'
: 'Please resolve your environment and test it with the CLI:';

const body = [
marker,
'',
header,
'',
`- Space repo: [${repoUrl}](${repoUrl})`,
`- Live URL: [${liveUrl}](${liveUrl})`,
'',
summary,
'',
'You can iterate locally or validate fixes by running `scripts/deploy_to_hf.sh --env "' + envName + '"`.'
].join('\n');

await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body
});
'- `openenv push ' + envName + ' --repo-id ' + repoId + '`',
];

const {owner, repo} = context.repo;
const issue_number = context.payload.pull_request.number;
const serverUrl = process.env.GITHUB_SERVER_URL || 'https://github.com';
const runUrl = `${serverUrl}/${owner}/${repo}/actions/runs/${context.runId}`;

if (status !== 'success') {
body.push(`- Failed run: ${runUrl}`);
}

const bodyText = body.join('\n');

const existing = await github.paginate(
github.rest.issues.listComments,
{ owner, repo, issue_number, per_page: 100 },
(response, done) => {
const match = response.data.find(comment => comment.body && comment.body.includes(marker));
if (match) {
done();
return [match];
}
return [];
}
);

if (existing.length > 0) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing[0].id,
body: bodyText,
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: bodyText,
});
}

- name: Delete preview space on Hugging Face
if: always()
continue-on-error: true
shell: bash
env:
SPACE_REPO_ID: ${{ steps.urls.outputs.space_repo_id }}
run: |
set -euo pipefail
if [ -z "${SPACE_REPO_ID:-}" ]; then
echo "No space repo id; skipping deletion"
exit 0
fi

TOKEN="${HF_TOKEN:-${HUGGINGFACEHUB_API_TOKEN:-}}"
if [ -z "$TOKEN" ]; then
echo "HF token not available; cannot delete space"
exit 0
fi

set +e
hf repo delete "$SPACE_REPO_ID" --repo-type space --yes --token "$TOKEN"
status=$?
set -e

if [ $status -eq 0 ]; then
echo "Deleted preview space $SPACE_REPO_ID"
else
echo "Failed to delete space $SPACE_REPO_ID (exit $status)"
fi

- name: Fail job if health check failed
if: steps.health_check.conclusion == 'failure'
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/publish-pypi-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ jobs:
- name: Build package
run: |
cd src/core
# Verify openenv_cli is accessible
ls -la ../openenv_cli || echo "Warning: openenv_cli directory not found"
python -m build

- name: Check package
Expand Down
114 changes: 113 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,118 @@ To use an environment:

See example scripts in `examples/` directory.

### Deploying Environments to Hugging Face Spaces

The OpenEnv CLI provides a self-service workflow for publishing environments to Hugging Face Spaces. This enables community members to share environments without requiring adding them as examples to this repo.

#### Installation

The CLI is installed as part of the OpenEnv package:

```bash
pip install -e .
```

#### Push Environment

Push an environment to Hugging Face Spaces:

```bash
openenv push <env_name> [options]
```

**Arguments:**
- `env_name`: Name of the environment to push (e.g., `echo_env`, `coding_env`)

**Options:**
- `--repo-id <repo_id>`: Hugging Face repository ID in format `namespace/space-name`. If not provided, uses `{username}/{env_name}`.
- `--private`: Create a private space (default: public)
- `--base-image <image>`: Base Docker image to use (default: `ghcr.io/meta-pytorch/openenv-base:latest`)
- `--dry-run`: Prepare files but don't upload to Hugging Face

**Examples:**

```bash
# Push echo_env to your personal namespace
openenv push echo_env

# Push to a specific organization
openenv push coding_env --repo-id my-org/coding_env

# Push with a custom space name
openenv push echo_env --repo-id my-org/my-custom-space

# Create a private space
openenv push echo_env --private

# Use a custom base image
openenv push echo_env --base-image ghcr.io/my-org/custom-base:latest

# Prepare files without uploading
openenv push echo_env --dry-run
```

#### How It Works

The `openenv push` command performs the following steps:

1. **Validation**: Checks that the environment exists in `src/envs/<env_name>/`
2. **Authentication**: Ensures you're authenticated with Hugging Face via interactive login (prompts if needed)
3. **Space Provisioning**: Determines the target Space repository ID (uses `--repo-id` if provided, otherwise `{username}/{env_name}`). Creates the Docker Space if needed (using `exist_ok=True` to handle existing spaces automatically)
4. **Build Process**:
- Creates a staging directory
- Copies core and environment files
- Generates/modifies Dockerfile with web interface enabled
- Prepares README: If the environment's README already has Hugging Face front matter (starts and ends with `---`), uses it as-is. Otherwise, generates front matter with random emoji and colors from approved options
5. **Deployment**: Uploads all files to the Hugging Face Space
6. **Cleanup**: Removes staging directory after successful upload

All pushed environments automatically include the web interface, available at `/web` on deployed spaces.

For more details on the CLI architecture and development, see [`src/openenv_cli/README.md`](src/openenv_cli/README.md).

## CLI Troubleshooting

### Authentication Issues

**Problem**: "Failed to retrieve token after login" or authentication errors

**Solution**:
- Check that `huggingface_hub` is properly installed: `pip install --upgrade huggingface_hub`
- Try logging in via the Hugging Face CLI: `huggingface-cli login`
- Clear cached credentials if needed (credentials are stored by `huggingface_hub`)
- Ensure you have "write" permissions on the namespace where you're pushing

### Space Creation Fails

**Problem**: "Failed to create space" or "Permission denied"

**Solution**:
- Check that namespace/username is correct
- Verify you have permission to create spaces in that namespace
- If the space already exists, `exist_ok=True` handles it automatically (you may see a warning from the Hub CLI)
- For authentication errors, see "Authentication Issues" above

### Upload Fails

**Problem**: "Failed to upload to space"

**Solution**:
- Check internet connection
- Verify you're still authenticated (may need to log in again)
- Try `--dry-run` first to check file preparation
- Check staging directory exists and has files
- Verify you have write permissions on the target space

### Environment Not Found

**Problem**: "Environment 'xyz' not found"

**Solution**:
- Verify environment exists in `src/envs/<env_name>/`
- Check spelling of environment name
- Ensure environment directory has required structure (models.py, server/, etc.)

## Design Principles

1. **Separation of Concerns**: Clear client-server boundaries
Expand Down Expand Up @@ -228,4 +340,4 @@ And we'd also like to acknowledge the team at Farama Foundation as the OpenEnv A

## License

BSD 3-Clause License (see LICENSE file)
BSD 3-Clause License (see LICENSE file)
14 changes: 13 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,23 @@ dependencies = [
"requests>=2.25.0",
"fastapi>=0.104.0",
"uvicorn>=0.24.0",
"smolagents>=1.22.0,<2"
"smolagents>=1.22.0,<2",
"huggingface_hub>=0.20.0",
"rich>=13.0.0",
]

[tool.setuptools]
package-dir = {"" = "src"}

[tool.setuptools.packages.find]
where = ["src"]

[project.scripts]
openenv = "openenv_cli.__main__:main"

[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"pytest-mock>=3.10.0",
"pytest-cov>=4.0.0"
]
14 changes: 12 additions & 2 deletions src/core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@ dependencies = [
"requests>=2.25.0",
"fastapi>=0.104.0",
"uvicorn>=0.24.0",
"huggingface_hub>=0.20.0",
"rich>=13.0.0",
"typer>=0.12.0",
]

[project.scripts]
openenv = "openenv_cli.__main__:main"

[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
Expand All @@ -41,6 +47,10 @@ packages = [
"openenv_core.containers",
"openenv_core.containers.runtime",
"openenv_core.env_server",
"openenv_core.tools"
"openenv_core.tools",
"openenv_cli",
"openenv_cli.commands",
"openenv_cli.core",
"openenv_cli.utils",
]
package-dir = {"openenv_core" = "."}
package-dir = {"openenv_core" = ".", "openenv_cli" = "../openenv_cli"}
4 changes: 2 additions & 2 deletions src/envs/atari_env/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
title: Atari Environment Server
emoji: 🕹️
colorFrom: '#FF6200'
colorTo: '#D4151B'
colorFrom: yellow
colorTo: red
sdk: docker
pinned: false
app_port: 8000
Expand Down
4 changes: 2 additions & 2 deletions src/envs/chat_env/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
title: Chat Environment Server
emoji: 💬
colorFrom: '#0084FF'
colorTo: '#25D366'
colorFrom: gray
colorTo: blue
sdk: docker
pinned: false
app_port: 8000
Expand Down
4 changes: 2 additions & 2 deletions src/envs/coding_env/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
title: Coding Environment Server
emoji: 💻
colorFrom: '#007ACC'
colorTo: '#1E1E1E'
colorFrom: gray
colorTo: indigo
sdk: docker
pinned: false
app_port: 8000
Expand Down
Loading
Loading