diff --git a/cli/root.go b/cli/root.go
index a40ac7c3c2..ed89a00ddc 100644
--- a/cli/root.go
+++ b/cli/root.go
@@ -343,10 +343,11 @@ func (r *RootCmd) Command(subcommands []*serpent.Command) (*serpent.Command, err
// support links.
return
}
- if cmd.Name() == "boundary" {
- // The boundary command is integrated from the boundary package
- // and has YAML-only options (e.g., allowlist from config file)
- // that don't have flags or env vars.
+ if cmd.Name() == "agent-firewall" || cmd.Name() == "boundary" {
+ // The agent-firewall command (and its "boundary" alias) is
+ // integrated from the boundary package and has YAML-only
+ // options (e.g., allowlist from config file) that don't
+ // have flags or env vars.
return
}
merr = errors.Join(
diff --git a/docs/ai-coder/agent-firewall/version.md b/docs/ai-coder/agent-firewall/version.md
index e8bdef5556..28de4d238c 100644
--- a/docs/ai-coder/agent-firewall/version.md
+++ b/docs/ai-coder/agent-firewall/version.md
@@ -13,12 +13,12 @@ v4.7.0 or newer**.
### Coder v2.30.0+
Since Coder v2.30.0, Agent Firewall is embedded inside the Coder binary, and
-you don't need to install it separately. The `coder boundary` subcommand is
+you don't need to install it separately. The `coder agent-firewall` subcommand is
available directly from the Coder CLI.
### Claude Code Module v4.7.0+
-Since Claude Code module v4.7.0, the embedded `coder boundary` subcommand is
+Since Claude Code module v4.7.0, the embedded `coder agent-firewall` subcommand is
used by default. This means you don't need to set `boundary_version`; the
boundary version is tied to your Coder version.
@@ -27,7 +27,7 @@ boundary version is tied to your Coder version.
### Using Coder Before v2.30.0 with Claude Code Module v4.7.0+
If you're using Coder before v2.30.0 with Claude Code module v4.7.0 or newer,
-the `coder boundary` subcommand isn't available in your Coder installation. In
+the `coder agent-firewall` subcommand isn't available in your Coder installation. In
this case, you need to:
1. Set `use_boundary_directly = true` in your Terraform module configuration
diff --git a/docs/manifest.json b/docs/manifest.json
index cbbeceefbe..a71fd205da 100644
--- a/docs/manifest.json
+++ b/docs/manifest.json
@@ -1626,9 +1626,9 @@
"path": "reference/cli/autoupdate.md"
},
{
- "title": "boundary",
+ "title": "agent-firewall",
"description": "Network isolation tool for monitoring and restricting HTTP/HTTPS requests",
- "path": "reference/cli/boundary.md"
+ "path": "reference/cli/agent-firewall.md"
},
{
"title": "coder",
diff --git a/docs/reference/cli/boundary.md b/docs/reference/cli/agent-firewall.md
similarity index 98%
rename from docs/reference/cli/boundary.md
rename to docs/reference/cli/agent-firewall.md
index 79af765679..add4098c6b 100644
--- a/docs/reference/cli/boundary.md
+++ b/docs/reference/cli/agent-firewall.md
@@ -1,12 +1,12 @@
-# boundary
+# agent-firewall
Network isolation tool for monitoring and restricting HTTP/HTTPS requests
## Usage
```console
-coder boundary [flags] [args...]
+coder agent-firewall [flags] [args...]
```
## Description
diff --git a/docs/reference/cli/index.md b/docs/reference/cli/index.md
index 211cba86c8..bbb7e85a31 100644
--- a/docs/reference/cli/index.md
+++ b/docs/reference/cli/index.md
@@ -66,7 +66,7 @@ Coder — A tool for provisioning self-hosted development environments with Terr
| [support](./support.md) | Commands for troubleshooting issues with a Coder deployment. |
| [server](./server.md) | Start a Coder server |
| [provisioner](./provisioner.md) | View and manage provisioner daemons and jobs |
-| [boundary](./boundary.md) | Network isolation tool for monitoring and restricting HTTP/HTTPS requests |
+| [agent-firewall](./agent-firewall.md) | Network isolation tool for monitoring and restricting HTTP/HTTPS requests |
| [features](./features.md) | List Enterprise features |
| [licenses](./licenses.md) | Add, delete, and list licenses |
| [groups](./groups.md) | Manage groups |
diff --git a/enterprise/cli/boundary.go b/enterprise/cli/boundary.go
index 104b2c6de2..212fe264ba 100644
--- a/enterprise/cli/boundary.go
+++ b/enterprise/cli/boundary.go
@@ -1,6 +1,7 @@
package cli
import (
+ "fmt"
"net/http"
"os"
"runtime/debug"
@@ -41,28 +42,31 @@ func (r *RootCmd) verifyLicense(inv *serpent.Invocation) error {
entitlements, err := client.Entitlements(inv.Context())
if cerr, ok := codersdk.AsError(err); ok && cerr.StatusCode() == http.StatusNotFound {
- return xerrors.Errorf("your deployment appears to be an AGPL deployment, so you cannot use the boundary command")
+ return xerrors.Errorf("your deployment appears to be an AGPL deployment, so you cannot use the agent-firewall command")
} else if err != nil {
return xerrors.Errorf("failed to get entitlements: %w", err)
}
feature := entitlements.Features[codersdk.FeatureBoundary]
if feature.Entitlement == codersdk.EntitlementNotEntitled {
- return xerrors.Errorf("your license is not entitled to use the boundary feature")
+ return xerrors.Errorf("your license is not entitled to use the agent-firewall feature")
}
if !feature.Enabled {
// Feature is entitled but disabled (shouldn't happen for FeatureBoundary
// since it's in AlwaysEnable(), but handle it gracefully).
- return xerrors.Errorf("the boundary feature is disabled in your deployment configuration")
+ return xerrors.Errorf("the agent-firewall feature is disabled in your deployment configuration")
}
return nil
}
-func (r *RootCmd) boundary() *serpent.Command {
+// buildAgentFirewallCmd builds the agent-firewall command. The returned command
+// uses the boundary base command from the external boundary package, wrapped
+// with license verification.
+func (r *RootCmd) buildAgentFirewallCmd() *serpent.Command {
version := getBoundaryVersion()
- cmd := boundarycli.BaseCommand(version) // Package coder/boundary/cli exports a "base command" designed to be integrated as a subcommand.
- cmd.Use += " [args...]" // The base command looks like `boundary -- command`. Serpent adds the flags piece, but we need to add the args.
+ cmd := boundarycli.BaseCommand(version)
+ cmd.Use = "agent-firewall [args...]"
// Wrap the handler to check for FeatureBoundary entitlement.
originalHandler := cmd.Handler
@@ -78,9 +82,42 @@ func (r *RootCmd) boundary() *serpent.Command {
return err
}
- // Call the original handler if entitlement check passes.
return originalHandler(inv)
}
return cmd
}
+
+// buildBoundaryAliasCmd builds a hidden, deprecated "boundary" command that
+// prints a deprecation notice and then runs the same logic as agent-firewall.
+func (r *RootCmd) buildBoundaryAliasCmd() *serpent.Command {
+ version := getBoundaryVersion()
+ cmd := boundarycli.BaseCommand(version)
+ cmd.Use = "boundary [args...]"
+ cmd.Hidden = true
+
+ originalHandler := cmd.Handler
+ cmd.Handler = func(inv *serpent.Invocation) error {
+ _, _ = fmt.Fprintln(inv.Stderr, "DEPRECATED: 'coder boundary' is deprecated, use 'coder agent-firewall' instead.")
+
+ if isChild() {
+ return originalHandler(inv)
+ }
+
+ if err := r.verifyLicense(inv); err != nil {
+ return err
+ }
+
+ return originalHandler(inv)
+ }
+
+ return cmd
+}
+
+func (r *RootCmd) agentFirewall() *serpent.Command {
+ return r.buildAgentFirewallCmd()
+}
+
+func (r *RootCmd) boundaryAlias() *serpent.Command {
+ return r.buildBoundaryAliasCmd()
+}
diff --git a/enterprise/cli/boundary_test.go b/enterprise/cli/boundary_test.go
index 2457f4ca63..0c8f4c7bc3 100644
--- a/enterprise/cli/boundary_test.go
+++ b/enterprise/cli/boundary_test.go
@@ -24,7 +24,25 @@ import (
// Actually testing the functionality of coder/boundary takes place in the
// coder/boundary repo, since it's a dependency of coder.
// Here we want to test basically that integrating it as a subcommand doesn't break anything.
-func TestBoundarySubcommand(t *testing.T) {
+func TestAgentFirewallSubcommand(t *testing.T) {
+ t.Parallel()
+
+ inv, _ := newCLI(t, "agent-firewall", "--help")
+ var buf bytes.Buffer
+ inv.Stdout = &buf
+ inv.Stderr = &buf
+
+ err := inv.Run()
+ require.NoError(t, err)
+
+ // Verify help output contains expected information.
+ // We're simply confirming that `coder agent-firewall --help` ran without a runtime error as
+ // a good chunk of serpent's self validation logic happens at runtime.
+ output := buf.String()
+ assert.Contains(t, output, boundarycli.BaseCommand("dev").Short)
+}
+
+func TestBoundaryAlias(t *testing.T) {
t.Parallel()
inv, _ := newCLI(t, "boundary", "--help")
@@ -35,14 +53,12 @@ func TestBoundarySubcommand(t *testing.T) {
err := inv.Run()
require.NoError(t, err)
- // Verify help output contains expected information.
- // We're simply confirming that `coder boundary --help` ran without a runtime error as
- // a good chunk of serpents self validation logic happens at runtime.
+ // The alias should dispatch to the same command and display help.
output := buf.String()
assert.Contains(t, output, boundarycli.BaseCommand("dev").Short)
}
-func TestBoundaryLicenseVerification(t *testing.T) {
+func TestAgentFirewallLicenseVerification(t *testing.T) {
t.Parallel()
t.Run("EntitledAndEnabled", func(t *testing.T) {
@@ -56,13 +72,13 @@ func TestBoundaryLicenseVerification(t *testing.T) {
},
})
- inv, conf := newCLI(t, "boundary", "--version")
+ inv, conf := newCLI(t, "agent-firewall", "--version")
//nolint:gocritic // requires owner
clitest.SetupConfig(t, client, conf)
ctx := testutil.Context(t, testutil.WaitShort)
err := inv.WithContext(ctx).Run()
- // Should succeed - boundary --version should work with valid license.
+ // Should succeed - agent-firewall --version should work with valid license.
require.NoError(t, err)
})
@@ -122,13 +138,13 @@ func TestBoundaryLicenseVerification(t *testing.T) {
proxyClient.SetSessionToken(client.SessionToken())
t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)
- inv, conf := newCLI(t, "boundary", "--version")
+ inv, conf := newCLI(t, "agent-firewall", "--version")
clitest.SetupConfig(t, proxyClient, conf)
ctx := testutil.Context(t, testutil.WaitShort)
err = inv.WithContext(ctx).Run()
require.Error(t, err)
- require.ErrorContains(t, err, "your license is not entitled to use the boundary feature")
+ require.ErrorContains(t, err, "your license is not entitled to use the agent-firewall feature")
})
t.Run("FeatureDisabled", func(t *testing.T) {
@@ -186,13 +202,13 @@ func TestBoundaryLicenseVerification(t *testing.T) {
proxyClient.SetSessionToken(client.SessionToken())
t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)
- inv, conf := newCLI(t, "boundary", "--version")
+ inv, conf := newCLI(t, "agent-firewall", "--version")
clitest.SetupConfig(t, proxyClient, conf)
ctx := testutil.Context(t, testutil.WaitShort)
err = inv.WithContext(ctx).Run()
require.Error(t, err)
- require.ErrorContains(t, err, "the boundary feature is disabled in your deployment configuration")
+ require.ErrorContains(t, err, "the agent-firewall feature is disabled in your deployment configuration")
})
t.Run("AGPLDeployment", func(t *testing.T) {
@@ -223,7 +239,7 @@ func TestBoundaryLicenseVerification(t *testing.T) {
proxyClient.SetSessionToken(client.SessionToken())
t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)
- inv, conf := newCLI(t, "boundary", "--version")
+ inv, conf := newCLI(t, "agent-firewall", "--version")
clitest.SetupConfig(t, proxyClient, conf)
ctx := testutil.Context(t, testutil.WaitShort)
@@ -233,11 +249,11 @@ func TestBoundaryLicenseVerification(t *testing.T) {
})
}
-// TestBoundaryChildProcessSkipsCheck verifies that when CHILD=true, the license
-// check is skipped. This simulates boundary re-executing itself to run the
-// target process. We use a proxy that would fail the license check to verify
-// it's skipped.
-func TestBoundaryChildProcessSkipsCheck(t *testing.T) {
+// TestAgentFirewallChildProcessSkipsCheck verifies that when CHILD=true, the
+// license check is skipped. This simulates boundary re-executing itself to run
+// the target process. We use a proxy that would fail the license check to
+// verify it's skipped.
+func TestAgentFirewallChildProcessSkipsCheck(t *testing.T) {
// Cannot use t.Parallel() with t.Setenv().
client, _ := coderdenttest.New(t, &coderdenttest.Options{
LicenseOptions: &coderdenttest.LicenseOptions{
@@ -290,7 +306,7 @@ func TestBoundaryChildProcessSkipsCheck(t *testing.T) {
proxyClient.SetSessionToken(client.SessionToken())
t.Cleanup(proxyClient.HTTPClient.CloseIdleConnections)
- inv, conf := newCLI(t, "boundary", "--version")
+ inv, conf := newCLI(t, "agent-firewall", "--version")
clitest.SetupConfig(t, proxyClient, conf)
// Set CHILD=true to simulate boundary re-execution. This should skip the
diff --git a/enterprise/cli/root.go b/enterprise/cli/root.go
index baba6830e6..b211c0d598 100644
--- a/enterprise/cli/root.go
+++ b/enterprise/cli/root.go
@@ -18,7 +18,8 @@ func (r *RootCmd) enterpriseOnly() []*serpent.Command {
agplcli.ExperimentalCommand(append(r.AGPLExperimental(), r.enterpriseExperimental()...)),
// New commands that don't exist in AGPL:
- r.boundary(),
+ r.agentFirewall(),
+ r.boundaryAlias(),
r.workspaceProxy(),
r.features(),
r.licenses(),
diff --git a/enterprise/cli/testdata/coder_--help.golden b/enterprise/cli/testdata/coder_--help.golden
index 1db07b1801..373a3609e4 100644
--- a/enterprise/cli/testdata/coder_--help.golden
+++ b/enterprise/cli/testdata/coder_--help.golden
@@ -14,9 +14,9 @@ USAGE:
$ coder templates init
SUBCOMMANDS:
- aibridge Manage AI Bridge.
- boundary Network isolation tool for monitoring and restricting
+ agent-firewall Network isolation tool for monitoring and restricting
HTTP/HTTPS requests
+ aibridge Manage AI Bridge.
external-workspaces Create or manage external workspaces
features List Enterprise features
groups Manage groups
diff --git a/enterprise/cli/testdata/coder_boundary_--help.golden b/enterprise/cli/testdata/coder_agent-firewall_--help.golden
similarity index 98%
rename from enterprise/cli/testdata/coder_boundary_--help.golden
rename to enterprise/cli/testdata/coder_agent-firewall_--help.golden
index 74f46947c1..5c6dcf7adb 100644
--- a/enterprise/cli/testdata/coder_boundary_--help.golden
+++ b/enterprise/cli/testdata/coder_agent-firewall_--help.golden
@@ -1,7 +1,7 @@
coder v0.0.0-devel
USAGE:
- coder boundary [flags] [args...]
+ coder agent-firewall [flags] [args...]
Network isolation tool for monitoring and restricting HTTP/HTTPS requests