Skip to content

feat(mcp): server management commands, self/atmos-pro presets, and config UX fixes#2720

Open
Erik Osterman (Cloud Posse) (osterman) wants to merge 45 commits into
mainfrom
osterman/mcp-handling-improvements
Open

feat(mcp): server management commands, self/atmos-pro presets, and config UX fixes#2720
Erik Osterman (Cloud Posse) (osterman) wants to merge 45 commits into
mainfrom
osterman/mcp-handling-improvements

Conversation

@osterman

@osterman Erik Osterman (Cloud Posse) (osterman) commented Jul 11, 2026

Copy link
Copy Markdown
Member

what

  • Extracts the standalone atmos mcp install command (client-config installer for AI coding assistants) from the stalled osterman/pro-install-cmd branch, leaving all Atmos Pro-specific wiring behind.
  • Adds full MCP server-management commands: atmos mcp add, atmos mcp remove, and atmos mcp uninstall, rounding out the existing list/install/export/status/test set.
    • add/remove only edit mcp.servers in atmos.yaml (the source of truth); install/uninstall push/pull that declared config to/from AI client config files (Claude Code, Cursor, VS Code, Codex, Gemini).
    • Adds built-in self and atmos-pro presets so atmos mcp add (no arguments) works out of the box, resolving to Atmos's own MCP server or the Atmos Pro MCP server respectively.
    • Adding the self preset interactively offers to flip mcp.enabled: true in atmos.yaml if it isn't already, since that preset requires it to function.
    • atmos mcp list/status now nudge users to run atmos mcp add atmos-pro when Atmos Pro is configured but its MCP server hasn't been added yet.
    • atmos mcp install with nothing configured now interactively offers to add+install the self preset instead of just printing a static hint.
  • Adds a new top-level /mcp docs landing page (website/docs/mcp/mcp.mdx) covering both directions — Atmos as an MCP client and as an MCP server — cross-linked from /ai via tabs, matching the existing /pro and /ai landing pages.
  • Expands atmos mcp install/uninstall's supported-client list from 5 to 15: adds Claude Desktop, Windsurf, Cline, Cline CLI, Zed, OpenCode, Goose, GitHub Copilot CLI, Antigravity, and MCPorter, alongside the existing Claude Code, Cursor, VS Code, Codex, and Gemini. Includes a new YAML config writer (for Goose) and bespoke per-client entry renderers where a client's schema differs from the common mcpServers shape (Zed's context_servers, OpenCode's mcp).
  • Adds a ## Troubleshooting section to the atmos mcp install docs covering post-install behavior that varies by client (some need a restart, others need a manual settings toggle, some auto-reload).
  • Fixes #2628: atmos ai skill install previously always wrote to ~/.atmos/skills/, so VS Code / GitHub Copilot's .github/skills/ couldn't discover installed skills.
    • Adds --path/ATMOS_AI_SKILL_PATH to override the install directory.
    • Adds zero-flag multi-client skill distribution: atmos ai skill install <name> now auto-detects which AI clients (Claude Code, VS Code/Copilot, Gemini) are in use in the current project and copies the skill into each one's well-known skill directory, mirroring atmos mcp install's detect/--client/--all-clients/interactive-picker UX. atmos ai skill uninstall gained the same --client/--all-clients symmetry to clean up those copies.
    • atmos ai skill install/uninstall with no <source>/<name> now acts on every skill (every bundled skill for install, every installed skill for uninstall) instead of requiring one invocation per skill — mirrors atmos mcp install/uninstall's existing "no server names given = act on everything configured" convention rather than adding a separate --all flag.
  • Adds atmos config set/delete/format UX polish:
    • Infers the value's type (bool, int, float, string, yaml) from the Atmos config schema via reflection on yaml/mapstructure struct tags, so atmos config set mcp.enabled true writes a real boolean without needing --type=bool.
    • Echoes the value that was written/deleted/formatted, and shows paths relative to the current directory instead of absolute.
  • Fixes a CLI error-message bug: a leaf command (no subcommands) whose Args validator rejects the wrong number of arguments (e.g. atmos config unset mcp.enabled true against ExactArgs(1)) previously reported the misleading Unknown command mcp.enabled — implying an unrecognized subcommand — when the real problem was too many arguments. Now surfaces Cobra's own accurate validator message.
  • Fixes toolchain installs on macOS: verifier binaries downloaded during atmos toolchain install can carry the com.apple.quarantine/com.apple.provenance xattrs, causing Gatekeeper to block execution even though the binary was already checksum/signature-verified. Strips the xattrs and ad-hoc re-signs the binary on darwin, with a verifier_trust: disabled opt-out.
  • Adds a "Native AWS SigV4 Signing" design section to docs/prd/atmos-mcp-integrations.md (proposal only, not implemented in this PR) for natively signing requests to AWS-hosted MCP servers using Atmos Auth identities, without a third-party mcp-proxy-for-aws bridge.

why

  • atmos mcp install's client-installer code was implemented on a branch entangled with unrelated, stalled Atmos Pro onboarding work; splitting it out lets the MCP work ship independently.
  • Early testing surfaced a real gap: there was no way to get Atmos's own MCP server, or the Atmos Pro MCP server, into an AI client's config without hand-editing atmos.yaml and running client-specific commands manually — atmos mcp add self/atmos mcp add atmos-pro collapse that to one command.
  • atmos config set mcp.enabled true silently writing the string "true" instead of a boolean is a footgun users would hit immediately while following the new MCP add/enable flow; schema-based type inference removes the need to know or pass --type=bool.
  • The "Unknown command" error was actively misleading during testing of this same feature (atmos config unset mcp.enabled true), and the root cause turned out to be a small, generally-applicable flaw in how Cobra Args-validator failures were reported for leaf commands.
  • The toolchain trust fix was discovered by a concurrent session working in this same worktree; bundling it into this PR (with its own commit and blog post) avoids a second churn cycle through CI/review for an unrelated one-line-symptom, multi-file fix.
  • The VS Code/Copilot skill-discovery gap and the narrow 5-client MCP list were both instances of the same underlying problem: Atmos only supported a handful of AI clients by name instead of the broad set real users are on. Expanding both to the same ~15-client list (sourced from add-mcp.com/docs/agents, which also supplied the model for the new Troubleshooting guidance) closes that gap once instead of client-by-client over time, and mirroring MCP's already-proven detect/--client/--all-clients/picker UX for skills keeps the two subsystems consistent for users learning either one.

references

Extracts the standalone `atmos mcp install` command and its supporting
MCP client/installer library changes from the stalled Atmos Pro install
branch (osterman/pro-install-cmd), leaving behind the Pro-specific
`--mcp` wiring on `atmos pro install` so this can ship independently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fills the missing Docusaurus page for the atmos mcp install command,
matching the existing mcp/export and mcp/list command doc conventions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a top-level MCP overview page (following the /pro and /ai landing
page precedent) documenting Atmos as an MCP client -- connecting to
external MCP servers and installing them into AI coding assistants --
and cross-linking to the existing MCP server (Atmos-as-server) docs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Restructures the /ai page's MCP section into Client/Server tabs that
both link to the dedicated /mcp page, and expands /mcp itself from a
client-only page into the actual unified hub the tabs point to (adds
an "As a Server" section alongside the existing client content).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Captures the design for signing requests to AWS's hosted HTTP MCP
servers natively via Atmos Auth identities, so the mcp-proxy-for-aws
third-party proxy is no longer required. Proposed, not yet
implemented -- open questions flagged for a follow-up design pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Verifier binaries downloaded during toolchain installs can carry the
com.apple.quarantine/com.apple.provenance xattrs, causing Gatekeeper to
block execution even though the binary was verified during download.
Strip the xattrs and ad-hoc re-sign on darwin so the verifier can run;
add an opt-out via verifier_trust: disabled for environments that need
one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
A leaf command (no subcommands) whose Args validator rejects extra/
missing positional arguments (e.g. `atmos config unset mcp.enabled
true` against ExactArgs(1)) previously reported the misleading
"Unknown command mcp.enabled" -- implying an unrecognized subcommand,
when the real problem is too many arguments. Cobra's Find() only stops
descending when no child command name matches, so for a leaf command a
failed Args validator can only mean the arguments are wrong, never an
unrecognized subcommand; surface Cobra's own validator message instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…relative paths

atmos config set previously always wrote strings unless --type was
passed explicitly, so `atmos config set mcp.enabled true` silently
wrote the string "true" instead of a boolean. Infer the type from the
Atmos config schema by reflecting on yaml/mapstructure struct tags when
the dot-path matches a known field, falling back to string for
unmodeled paths (e.g. vars.*). Also echo the value that was written and
the deleted/formatted file, and show paths relative to the current
directory instead of absolute.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Round out MCP client management with atmos mcp add/remove (declare
servers in atmos.yaml) and atmos mcp install/uninstall (push/pull to AI
client config files), plus built-in self and atmos-pro presets so
`atmos mcp add` works with zero arguments. Adds an interactive prompt to
enable mcp.enabled when adding the self preset, and a nudge suggesting
atmos-pro when Atmos Pro is configured but not yet added.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@atmos-pro

atmos-pro Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Tip

Atmos Pro  

No affected stacks workflow was detected for this pull request.
If this is expected, no action is needed.
Learn More. Ask AI.

@osterman Erik Osterman (Cloud Posse) (osterman) added the minor New features that do not break anything label Jul 11, 2026
@github-actions github-actions Bot added the size/l Large size PR label Jul 11, 2026
@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Files

  • go.mod
  • website/pnpm-lock.yaml

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@osterman, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 11 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 06885e66-ae29-439e-bf71-8708d78ea9ee

📥 Commits

Reviewing files that changed from the base of the PR and between 8f43b63 and 7195a60.

📒 Files selected for processing (62)
  • cmd/cmd_utils.go
  • cmd/cmd_utils_test.go
  • cmd/config/operations.go
  • cmd/mcp/client/add.go
  • cmd/mcp/client/add_test.go
  • cmd/mcp/client/install.go
  • cmd/mcp/client/install_test.go
  • cmd/mcp/client/list.go
  • cmd/mcp/client/markdown/atmos_mcp_add.md
  • cmd/mcp/client/markdown/atmos_mcp_install.md
  • cmd/mcp/client/markdown/atmos_mcp_remove.md
  • cmd/mcp/client/markdown/atmos_mcp_uninstall.md
  • cmd/mcp/client/messages.go
  • cmd/mcp/client/messages_test.go
  • cmd/mcp/client/remove.go
  • cmd/mcp/client/remove_test.go
  • cmd/mcp/client/status.go
  • cmd/mcp/client/uninstall.go
  • cmd/mcp/client/uninstall_test.go
  • cmd/mcp/mcpcmd/markdown/atmos_mcp.md
  • cmd/mcp/server/start.go
  • cmd/root.go
  • docs/prd/atmos-mcp-integrations.md
  • pkg/config/schema_type.go
  • pkg/config/schema_type_test.go
  • pkg/mcp/client/config.go
  • pkg/mcp/client/config_test.go
  • pkg/mcp/client/mcpconfig.go
  • pkg/mcp/client/mcpconfig_test.go
  • pkg/mcp/client/session.go
  • pkg/mcp/config/config.go
  • pkg/mcp/config/config_test.go
  • pkg/mcp/config/presets.go
  • pkg/mcp/config/presets_test.go
  • pkg/mcp/install/install.go
  • pkg/mcp/install/install_test.go
  • pkg/schema/mcp.go
  • pkg/schema/mcp_test.go
  • pkg/schema/schema.go
  • pkg/toolchain/installer/errors.go
  • pkg/toolchain/installer/installer.go
  • pkg/toolchain/installer/verifier_command_test.go
  • pkg/toolchain/installer/verifier_trust_darwin.go
  • pkg/toolchain/installer/verifier_trust_darwin_test.go
  • pkg/toolchain/installer/verifier_trust_other.go
  • pkg/toolchain/verification/types.go
  • pkg/toolchain/verification/types_test.go
  • tests/snapshots/TestCLICommands_atmos_about_non-existent.stderr.golden
  • website/blog/2026-07-11-macos-toolchain-verifier-trust.mdx
  • website/blog/2026-07-11-mcp-server-management-commands.mdx
  • website/docs/ai/ai.mdx
  • website/docs/ai/mcp-server.mdx
  • website/docs/cli/commands/config/config-set.mdx
  • website/docs/cli/commands/mcp/add.mdx
  • website/docs/cli/commands/mcp/install.mdx
  • website/docs/cli/commands/mcp/remove.mdx
  • website/docs/cli/commands/mcp/uninstall.mdx
  • website/docs/cli/configuration/toolchain/index.mdx
  • website/docs/cli/configuration/toolchain/verification.mdx
  • website/docs/mcp/mcp.mdx
  • website/sidebars.js
  • website/src/data/roadmap.js
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch osterman/mcp-handling-improvements

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

Resource Changes Found for bucket in test

Atmos CI

create

Plan: 4 to add, 0 to change, 0 to destroy.
To reproduce this locally, run:

atmos terraform plan bucket -s test

Create

+ aws_s3_bucket.checkov_target
+ aws_s3_bucket.this
+ aws_s3_bucket.trivy_target
+ aws_s3_bucket_public_access_block.trivy_target
Terraform Plan Summary
  # aws_s3_bucket.checkov_target will be created
  + resource "aws_s3_bucket" "checkov_target" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "atmos-native-ci-e2e-checkov-test"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags_all                    = (known after apply)
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + cors_rule (known after apply)

      + grant (known after apply)

      + lifecycle_rule (known after apply)

      + logging (known after apply)

      + object_lock_configuration (known after apply)

      + replication_configuration (known after apply)

      + server_side_encryption_configuration (known after apply)

      + versioning (known after apply)

      + website (known after apply)
    }

  # aws_s3_bucket.this will be created
  + resource "aws_s3_bucket" "this" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "atmos-native-ci-e2e-test"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags                        = {
          + "AtmosFixture" = "native-ci-e2e"
          + "Stage"        = "test"
        }
      + tags_all                    = {
          + "AtmosFixture" = "native-ci-e2e"
          + "Stage"        = "test"
        }
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + cors_rule (known after apply)

      + grant (known after apply)

      + lifecycle_rule (known after apply)

      + logging (known after apply)

      + object_lock_configuration (known after apply)

      + replication_configuration (known after apply)

      + server_side_encryption_configuration (known after apply)

      + versioning (known after apply)

      + website (known after apply)
    }

  # aws_s3_bucket.trivy_target will be created
  + resource "aws_s3_bucket" "trivy_target" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "atmos-native-ci-e2e-trivy-test"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags_all                    = (known after apply)
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + cors_rule (known after apply)

      + grant (known after apply)

      + lifecycle_rule (known after apply)

      + logging (known after apply)

      + object_lock_configuration (known after apply)

      + replication_configuration (known after apply)

      + server_side_encryption_configuration (known after apply)

      + versioning (known after apply)

      + website (known after apply)
    }

  # aws_s3_bucket_public_access_block.trivy_target will be created
  + resource "aws_s3_bucket_public_access_block" "trivy_target" {
      + block_public_acls       = true
      + block_public_policy     = true
      + bucket                  = (known after apply)
      + id                      = (known after apply)
      + ignore_public_acls      = true
      + restrict_public_buckets = true
    }

Plan: 4 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + bucket_name = "atmos-native-ci-e2e-test"

@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.10236% with 792 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.66%. Comparing base (50947fa) to head (397dff6).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
cmd/mcp/client/install.go 38.09% 121 Missing and 9 partials ⚠️
cmd/mcp/client/add.go 34.75% 90 Missing and 2 partials ⚠️
pkg/ai/skills/marketplace/installer.go 70.65% 48 Missing and 28 partials ⚠️
cmd/ai/skill/clients.go 35.71% 68 Missing and 4 partials ⚠️
pkg/ai/tools/atmos/vendor_update_common.go 19.35% 50 Missing ⚠️
pkg/ai/tools/atmos/terraform_hcl_edit.go 78.06% 28 Missing and 6 partials ⚠️
pkg/ai/tools/atmos/secret_list.go 79.72% 22 Missing and 8 partials ⚠️
pkg/ai/tools/atmos/list_values.go 77.60% 22 Missing and 6 partials ⚠️
pkg/ai/tools/atmos/validate_file.go 83.82% 13 Missing and 9 partials ⚠️
pkg/ai/tools/atmos/list_components.go 80.00% 17 Missing and 4 partials ⚠️
... and 39 more

❌ Your patch check has failed because the patch coverage (81.10%) is below the target coverage (85.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2720      +/-   ##
==========================================
+ Coverage   81.61%   81.66%   +0.04%     
==========================================
  Files        1639     1710      +71     
  Lines      154739   161092    +6353     
==========================================
+ Hits       126294   131548    +5254     
- Misses      21527    22350     +823     
- Partials     6918     7194     +276     
Flag Coverage Δ
unittests 81.66% <81.10%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
cmd/ai/ai.go 100.00% <100.00%> (ø)
cmd/ai/ask.go 75.00% <100.00%> (-2.50%) ⬇️
cmd/ai/exec.go 83.33% <100.00%> (ø)
cmd/ai/sessions.go 88.20% <100.00%> (-1.03%) ⬇️
cmd/cmd_utils.go 69.63% <100.00%> (+0.13%) ⬆️
cmd/mcp/client/export.go 72.72% <100.00%> (-1.19%) ⬇️
cmd/mcp/client/start_options.go 68.75% <ø> (+1.18%) ⬆️
cmd/root.go 78.29% <100.00%> (+0.08%) ⬆️
cmd/vendor/config.go 83.47% <100.00%> (+1.23%) ⬆️
errors/errors.go 100.00% <ø> (ø)
... and 103 more

... and 38 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…CP client commands

atmos stack config {get,set,delete,format,list} and atmos vendor config
{get,set,delete,format,list} reuse the DisplayPath/type-inference machinery
from atmos config set. pkg/yaml/edit.go gains the shared read/write/delete
helpers both command families and their AI-tool counterparts build on.

Also continues the atmos mcp add/install/uninstall command work: registry
lookups, install/uninstall symmetry, and config validation fixes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…om ai.tools.enabled

atmos ai chat failed with an Anthropic API-key error even when a CLI provider
(claude-code/codex-cli/copilot-cli/gemini-cli) was already installed and
authenticated. ai.GetProvider now probes PATH for one before falling back to
anthropic, so ai.enabled: true alone is enough to start using an existing
subscription -- explicit default_provider/--provider still always wins.

atmos mcp start also required ai.tools.enabled on top of its own mcp.enabled
opt-in, which was pure friction: MCP's job is exposing tools to external
clients, unrelated to atmos ai chat's own tool-use loop. Tools now always
register for MCP once mcp.enabled: true.

Renamed ai.tools.{allowed_tools,restricted_tools,blocked_tools} to
{allowed,restricted,blocked} (no back-compat shim) and gave `allowed` a
second job: when non-empty it now also gates which tools are registered at
all, not just whether they skip the confirmation prompt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Expands the Atmos AI/MCP tool surface with atmos_config_*, atmos_stack_config_*,
atmos_vendor_config_*, atmos_auth_whoami, atmos_auth_list, atmos_list_commands,
atmos_command_help, atmos_describe_dependents, atmos_describe_workflows,
atmos_list_workflows, atmos_list_components, atmos_list_values,
atmos_toolchain_list, atmos_secret_list, and atmos_validate_component --
mirroring the corresponding CLI commands so an AI assistant or MCP client can
introspect and manage config, stacks, vendoring, auth, and workflows without
shelling out.

Also adds registration-time allow-list filtering (isToolAllowed) to
RegisterTools/registerAll: when ai.tools.allowed is non-empty, only matching
tools are registered at all, not just exempted from confirmation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a welcome flow for new/empty Atmos projects (chat_welcome.go) instead of
just reporting "no stacks found" -- the executor's system prompt gets the same
treatment so atmos ai exec offers to scaffold a first stack/component too.
Also refines session/provider handling and turn-step rendering in the chat TUI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wraps the --failure-mode/--log-order validation errors as static sentinels
(err113) and extracts the "plan"/"destroy" subcommand string literals to
named constants (revive) -- unrelated to this branch's other work, but
blocking on push since the pre-push hook lints the full repo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions github-actions Bot added size/xxl and removed size/l Large size PR labels Jul 12, 2026
@mergify

mergify Bot commented Jul 12, 2026

Copy link
Copy Markdown

Warning

This PR exceeds the recommended limit of 10,000 lines.

Large PRs are difficult to review and may be rejected due to their size.

Please verify that this PR does not address multiple issues.
Consider refactoring it into smaller, more focused PRs to facilitate a smoother review process.

@mergify

mergify Bot commented Jul 12, 2026

Copy link
Copy Markdown

💥 This pull request now has conflicts. Could you fix it Erik Osterman (Cloud Posse) (@osterman)? 🙏

`atmos ai skill install --all` didn't exist and there was no way to install
every bundled skill in one shot short of looping `atmos ai skill install
<name>` per skill, or fetching the whole cloudposse/atmos repo over the
network. Rather than bolt on a separate --all flag, this mirrors atmos mcp
install/uninstall's existing convention: omitting the positional argument
means "act on everything" (mcp install/uninstall already do this for
configured servers). `atmos ai skill install` with no <source> now installs
every skill in the embedded catalog (single confirmation, not one per skill,
skipping already-installed skills unless --force). `atmos ai skill uninstall`
with no <name> mirrors this to remove every installed skill.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… comma list

`atmos ai skill install`/`uninstall` (and multi-skill package install) print
a "Discovered N skills: a, b, c, ..." summary. With dozens of bundled skills
this comma-joined string got routed through the same markdown word-wrap used
for regular prose messages, which doesn't treat hyphens as safe break points
and produced garbled output like "atmos-\naws-ecr," mid-identifier.

Adds ui.FormatColumns(), a small ls-style column-major grid layout (evenly
spaced, sized to the terminal width, falling back to 80 columns when no real
width is detected), and a shared printItemColumns() helper that writes the
header via the normal icon/markdown path but the grid itself as plain text so
it isn't reflowed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Resolves a conflict in cmd/root.go between main's refactor of
initCobraConfig/SetUsageFunc/SetHelpFunc into named helper functions
and this branch's leaf-command arg-count error handling, by applying
the leaf-command special case inside main's new rootUsageFunc.
…teractive

atmos mcp install/uninstall already had --scope project|user and --global,
but silently defaulted to project whenever neither was passed -- it never
asked. resolveInstallScope now returns (string, error) and, when running
interactively (real TTY, not --yes, not CI) with neither flag explicitly set,
shows a single-choice scope picker before the existing client picker,
mirroring promptForMCPClients's form/theme/cancel-handling. Also fixes
resolveInstallScope not checking cmd.Flags().Changed("global") -- it
previously read the bool value directly, unable to distinguish "not passed"
from "explicitly passed as false".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…/--global

Fixes a real incident: running atmos ai skill install/uninstall from this
repo's own root wrote through (install) and deleted (uninstall) its
intentional .claude/skills/<name> symlinks, which point into
agent-skills/skills/<name> for contributor auto-discovery (per
docs/prd/atmos-agent-skills.md). distributeToClients/removeClientCopies now
check isSymlink() on the target before writing or deleting, skipping that
client with a warning instead -- matching the existing refuse-to-delete
pattern at pkg/terraform/clean/deleter.go and the skip-symlink-on-copy
pattern at internal/exec/copy_glob.go. The same unguarded os.RemoveAll
existed on the canonical --force reinstall path (prepareInstallPath); it now
returns the existing ErrRefuseDeleteSymbolicLink sentinel instead of silently
unlinking whatever's there.

Also adds --scope project|user and --global to atmos ai skill
install/uninstall, mirroring atmos mcp install/uninstall, since Claude Code,
Gemini CLI, and GitHub Copilot all document both a project-local and a
personal/user-level skill directory. Distribution now resolves scope-aware
paths (~/.claude/skills, ~/.copilot/skills for the vscode client -- distinct
from its project-level .github/skills, ~/.gemini/skills), and when neither
flag is given interactively, prompts to choose (mirrors the just-added MCP
scope prompt); a non-TTY session, --yes/--force, or CI still defaults to
project with no prompt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Separates the error block from whatever was printed before it (e.g. output
from the command that failed), instead of running directly into it with no
visual break. Regenerated the affected CLI snapshot goldens to match.
…cope prompt

atmos ai skill install (installing the whole bundled catalog, or --force
against an already-fully-installed catalog) printed 2 lines per skill
(per-client distribution + per-skill success) -- ~94 lines for 47 skills.
distributeToClients now returns which clients it actually distributed to
instead of printing per call; batch installers (InstallAllBundled,
installMultiSkillPackage) resolve and announce the client list once up
front instead of once per skill, and no longer print a redundant per-skill
success line -- only failures are worth a line, the batch summary already
covers success. Also stopped printing the "switch skills with Ctrl+A" usage
hint when zero skills were actually installed (e.g. everything was already
installed and skipped).

Also fixes atmos ai skill install --force silently skipping the new scope
prompt: resolveSkillScope read "yes"/"force" directly from viper, ignoring
which command it belonged to -- but install's --force means "reinstall",
not "skip prompts" (that's --yes), while uninstall has no separate --yes so
--force is its only skip signal. resolveSkillScope now takes an explicit
skipPrompt bool, mirroring resolveSkillClients's existing pattern, with each
caller passing its own correct flag.

Also adds backtick code-formatting to skill/client names, flags, and paths
in the affected toast messages, since they render as styled inline code
rather than plain text.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… and what changed

atmos ai skill install is always run from a plain shell, never from inside
atmos ai chat, so "Usage: Run atmos ai chat and switch skills with Ctrl+A"
never made sense there -- removed entirely (single-skill and batch install
paths).

The batch summary ("N skills installed successfully") also didn't say where
skills landed, and collapsed "freshly installed", "--force reinstalled an
existing skill", and "skipped because already installed" into a single
opaque count. installOneBundledSkill/installOneSkillFromPackage now report a
three-way outcome (installed/updated/skipped) instead of a bool, and the
batch summary reads accordingly:

  47 skills installed successfully
  Location: ~/.atmos/skills

  47 skills updated successfully          (all were --force reinstalls)
  Location: ~/.atmos/skills

  47 skills installed successfully (46 new, 1 updated)
  Location: ~/.atmos/skills

  0 skills installed
  47 already installed (use --force to reinstall)   (no Location: nothing landed)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…xed width

resolveFormatterWidth() computed the real detected terminal width but never
returned it -- it always used the configured max_width verbatim whenever set,
even when the actual terminal was narrower. With this repo's atmos.yaml
setting max_width: 120, every error's padded content (including the title
pill) was rendered at exactly 120 columns regardless of the real terminal
width, so in any narrower terminal it wrapped onto extra visual lines,
making the gap between the title and message look much bigger than intended.

Now resolves to min(configured_max, detected_width), matching max_width's
documented "maximum" semantics.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e a spinner

atmos ai skill uninstall --force (no explicit --scope) silently defaulted
scope to "project" -- the registry never tracks which scope a skill was
actually distributed to. Combined with running --force from a directory that
happens to have its own client signal (e.g. this repo's own .claude/), this
guessed wrong and could target unrelated real files at the wrong-but-real
project path instead of the actual (user-scope) distributed copies -- which
is exactly how .claude/skills/atmos-asciicast (the one entry there that isn't
a symlink like its siblings) got deleted. Uninstall now checks both project
and user scope whenever scope isn't explicit, so the real distributed copy
is never silently missed or mistargeted; removeClientCopies no-ops for
whichever scope nothing was actually distributed to.

Checking both scopes means legitimately encountering symlinks unrelated to
the current session (e.g. this repo's own bundled-skill symlinks). Rather
than warning once per skill -- alarming and confusing when most of them are
for a scope the user never touched -- removeClientCopies now returns a count
and the caller reports one aggregate "N client-distributed copies left in
place (symbolic link)" line.

Batch install (InstallAllBundled/installMultiSkillPackage) now wraps the
per-skill loop in a progress spinner ("Distributing to: ...") that resolves
to the final tally, instead of printing two separate static lines. The
completion message always names the actual client + directory (e.g. "→
claude-code (~/.claude/skills)") rather than leaving that to the spinner's
progress text, since non-TTY output (CI, piped, tests) only ever shows the
completion message.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The macos-intel Build job failed on a transient DNS resolution failure
reaching github.com while go mod download fetched a few indirect
dependencies not indexed on the Go module proxy (gopsutil, wazero, etc.).
The existing retry (3 attempts, constant 10s delay) only covered ~20-30s of
runway, which wasn't enough to ride out the outage -- all 3 attempts failed
within the same DNS blip, failing the whole build leg and cascading into
skipped downstream jobs (e.g. k3s-required's matrix gate).

Switch to exponential backoff (5 attempts, 5s initial delay, 60s cap),
matching this repo's own documented convention for network-flaky steps
(website/docs/workflows/workflows/workflow/steps/retry.mdx). This gives a
DNS blip up to ~2 minutes of growing backoff to clear instead of ~20s.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nk-line fix

errors/formatter.go's leading blank line (ad4d1e4) needed 58 golden
snapshots regenerated, but only ~20 were updated at the time -- the rest
were skipped because this sandbox's terminal-width detection differs from
the CI reference environment, and blindly regenerating them mixed in
unrelated word-wrap changes. These 28 were the ones left behind, and they're
exactly what failed CI (Acceptance Tests, linux + macos): every added line
here is a blank line before an error/workflow-error header, hand-verified
against the original wrapping to exclude the sandbox's width-detection noise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…y leading newline

Two independent regressions from the leading-blank-line fix in
errors.Format(), both only reproducible with color enabled (this sandbox
runs plain by default, so they passed locally but failed in CI):

- cmd/ai/skill install/uninstall tests asserted substrings like
  "~/.atmos/skills" or "0 skills installed" directly against captured UI
  output. Under color, styled runs are wrapped in separate ANSI spans (e.g.
  "~" and "/.atmos/skills" end up in adjacent-but-distinct spans), splitting
  the literal substring and failing a naive Contains check. Strip ANSI
  codes before asserting, matching the atmosansi.Strip convention already
  used elsewhere in these test files. Reproduced and verified fixed locally
  with CLICOLOR_FORCE=1.

- pkg/ai/analyze.Context.RunAnalysis appends errUtils.Format()'s raw result
  (now leading with a blank line, meant to visually separate the error from
  prior terminal output) into the captured Stderr sent to the AI provider.
  That leading blank line is a display-only concern and doesn't belong in
  the AI-facing capture, so trim it there specifically -- the actual
  terminal write on the line above is untouched and still gets the blank
  line.

Also drops resolveSkillClients' now-constant title parameter (unparam):
uninstall moved to its own resolveUninstallClients in an earlier commit,
so install.go is its only remaining caller and the title never varies.

Two other CI failures in this run (a `registry.opentofu.org` module lookup
timeout in a real `tofu init` during an acceptance test, Linux-only) are
unrelated transient network flakiness with no existing retry mechanism to
tune -- left alone rather than guessing at a fix I can't verify.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ic MCP client paths in tests

redactHomePath built the redacted display path via raw string prefix+trim
("~" + strings.TrimPrefix(path, homeDir)), which loses the separator
between "~" and the remainder whenever homeDir itself carries a trailing
separator (observed on Windows CI: homedir.Dir() returned one, producing
"~.atmos\skills" instead of "~\.atmos\skills" and failing two cmd/ai/skill
install tests' Contains checks). Rebuilt it on filepath.Rel, which handles
trailing-separator and normalization differences correctly regardless of
platform, and always joins with exactly one separator.

Also fixes two unrelated, pre-existing Windows-only failures in
pkg/mcp/install, exposed by this same CI run:

- TestResolveTarget_NewProjectAndUserScopeClients asserted a single
  cross-platform suffix for Zed's and Goose's user-scope config path, but
  zedUserPath/gooseUserPath correctly use each tool's real, documented
  Windows locations (%APPDATA%\Zed\settings.json and
  %APPDATA%\Block\goose\config\config.yaml) which differ in casing and
  directory structure from the Unix path. Added Windows-specific expected
  suffixes rather than changing the (correct) implementation.

- TestInstallJSONTarget_ClaudeDesktopGlobalScope: claudeDesktopUserPath
  intentionally prefers the real %APPDATA% env var over the injected
  homeDir on Windows, to match where Claude Desktop itself actually reads
  config from -- but the test never overrode %APPDATA%, so on a real
  Windows runner it was reading/writing the CI runner's actual user
  profile instead of the test's isolated temp dir, and cross-test
  pollution there was masking new file creation. Set %APPDATA% to a
  subdirectory of the test's temp home (a no-op on non-Windows).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…'t eat Windows separators

redactHomePath already builds a correct ~-prefixed path (fixed in 8c187c7), but
ui.Successf/ui.Infof render their whole message through the project's CommonMark-based
Markdown formatter, which treats a backslash before punctuation (e.g. the "\." before a
dot-directory like ".atmos") as an escape and silently drops the backslash -- while a
backslash before a letter (e.g. "\s") is left alone. On Windows this turned
"~\.atmos\skills" into "~.atmos\skills", still failing
TestInstallCmd_RunE_NoArgsInstallsEveryBundledSkill and
TestInstallCmd_RunE_DistributingToShowsRealClientDirectory on the same commit that
was supposed to fix them. CommonMark code spans are exempt from backslash-escape
processing, so wrapping the redacted path in backticks (matching the existing
convention for client names) fixes the display without touching redactHomePath itself,
whose raw return value two existing unit tests assert on directly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mergify

mergify Bot commented Jul 14, 2026

Copy link
Copy Markdown

💥 This pull request now has conflicts. Could you fix it Erik Osterman (Cloud Posse) (@osterman)? 🙏

@mergify mergify Bot added the conflict This PR has conflicts label Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conflict This PR has conflicts minor New features that do not break anything size/xxl

Projects

None yet

Development

Successfully merging this pull request may close these issues.

atmos ai skill does not support vscode natively

1 participant