From fa7b067361e488d7f023eadbbb79010db12e0cb2 Mon Sep 17 00:00:00 2001 From: Facundo Farias Date: Tue, 30 Jun 2026 14:15:11 +0200 Subject: [PATCH 1/3] chore(setup): deprecate 'dhq setup' in favor of 'dhq skills' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'dhq skills install' supersedes 'dhq setup': it auto-detects installed agents and covers 12 of them, vs the 4 hard-coded subcommands here. The two now overlap, which is confusing. Mark 'dhq setup' deprecated without breaking it: - Each subcommand prints a yellow deprecation warning on every run (stderr, so JSON/data on stdout is unaffected) pointing at the exact equivalent 'dhq skills install --agent '. - Parent and per-agent help text carry a DEPRECATED notice. - README marks 'dhq setup' deprecated in the command list and the Agent Integration section. Behaviour is otherwise unchanged — existing scripts keep working until a future release removes the command. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 8 ++++---- internal/commands/setup.go | 31 ++++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f6fea03..3deb769 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ dhq completion bash | zsh | fish | powershell dhq doctor (health check) dhq update (self-update to latest version) dhq skills list | install (auto-detect AI agents and install the DeployHQ skill) -dhq setup claude | codex | cursor | windsurf (install agent plugins, --project for project-level) +dhq setup claude | codex | cursor | windsurf (deprecated — use 'dhq skills') dhq mcp (start MCP server in stdio mode) ``` @@ -318,9 +318,9 @@ bare `dhq skills install`; **project-scope** agents write into the current repository and require an explicit `--agent` flag so login never mutates a repo as a side effect. -`dhq setup ` is a narrower alternative that installs the agent plugin for -a single tool (Claude Code, Codex, Cursor, or Windsurf), with `--project` for -project-level installs. +> **Deprecated:** `dhq setup ` (Claude Code, Codex, Cursor, Windsurf) is +> the older, narrower predecessor of `dhq skills install`. It still works but +> warns on use and will be removed in a future release — prefer `dhq skills`. ### Other agent helpers diff --git a/internal/commands/setup.go b/internal/commands/setup.go index de0ca99..e230170 100644 --- a/internal/commands/setup.go +++ b/internal/commands/setup.go @@ -45,6 +45,7 @@ type agentSetup struct { Use string Short string Name string + SkillsName string // equivalent target name for `dhq skills install --agent` PathFor func(scope) (string, error) Content func() []byte StrategyFor func(scope) writeStrategy @@ -59,6 +60,7 @@ var agents = []agentSetup{ Use: "claude", Short: "Install Claude Code skill", Name: "Claude Code", + SkillsName: "claude-code", PathFor: pathClaude, Content: func() []byte { return []byte(skillFrontmatter + skillBody) }, StrategyFor: always(strategyOverwrite), @@ -67,6 +69,7 @@ var agents = []agentSetup{ Use: "codex", Short: "Install OpenAI Codex AGENTS.md section", Name: "Codex", + SkillsName: "codex", PathFor: pathCodex, Content: func() []byte { return []byte(skillBody) }, StrategyFor: always(strategyMarkedBlock), @@ -75,15 +78,17 @@ var agents = []agentSetup{ Use: "cursor", Short: "Install Cursor project rule", Name: "Cursor", + SkillsName: "cursor", PathFor: pathCursor, Content: func() []byte { return []byte(cursorFrontmatter + skillBody) }, StrategyFor: always(strategyOverwrite), }, { - Use: "windsurf", - Short: "Install Windsurf integration", - Name: "Windsurf", - PathFor: pathWindsurf, + Use: "windsurf", + Short: "Install Windsurf integration", + Name: "Windsurf", + SkillsName: "windsurf", + PathFor: pathWindsurf, Content: func() []byte { return []byte(skillBody) }, // User-level writes into the shared ~/.codeium/.../global_rules.md, so we // must merge with a marker block. Project-level writes a dedicated file we own. @@ -99,8 +104,12 @@ var agents = []agentSetup{ func newSetupCmd() *cobra.Command { cmd := &cobra.Command{ Use: "setup", - Short: "Install agent plugins", - Long: "Install DeployHQ agent integration files for AI coding assistants.", + Short: "Install agent plugins (deprecated — use 'dhq skills')", + Long: "Install DeployHQ agent integration files for AI coding assistants.\n\n" + + "DEPRECATED: 'dhq setup' is superseded by 'dhq skills install', which\n" + + "auto-detects installed agents and supports 12 of them (vs the 4 here).\n" + + "This command still works but will be removed in a future release.\n" + + "Migrate with 'dhq skills install' (or 'dhq skills install --agent ').", } for _, a := range agents { cmd.AddCommand(newAgentSetupCmd(a)) @@ -118,6 +127,14 @@ func newAgentSetupCmd(a agentSetup) *cobra.Command { Long: longHelp(a), Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { + env := cliCtx.Envelope + // 'dhq setup' is deprecated in favour of 'dhq skills'. Warn on every + // use (stderr, so JSON/data on stdout is unaffected) and point at the + // equivalent command rather than silently doing the old thing. + env.Warn("'dhq setup' is deprecated; use 'dhq skills install --agent %s' instead "+ + "(auto-detects agents and supports 12 of them). 'dhq setup' will be removed "+ + "in a future release.", a.SkillsName) + sc := scopeUser if project { sc = scopeProject @@ -133,7 +150,6 @@ func newAgentSetupCmd(a agentSetup) *cobra.Command { return &output.InternalError{Message: "resolve install path", Cause: err} } - env := cliCtx.Envelope strategy := a.StrategyFor(sc) if uninstall { return runUninstall(env, a, path, strategy) @@ -154,6 +170,7 @@ func longHelp(a agentSetup) string { var b strings.Builder fmt.Fprintf(&b, "Install %s integration files.\n\n", a.Name) + fmt.Fprintf(&b, "DEPRECATED: use 'dhq skills install --agent %s' instead.\n\n", a.SkillsName) if userErr == nil { fmt.Fprintf(&b, "Default (user-global): %s\n", userPath) } else { From eec549c25d8998d87725a8e604785cb244d3d80f Mon Sep 17 00:00:00 2001 From: Facundo Farias Date: Tue, 30 Jun 2026 16:36:26 +0200 Subject: [PATCH 2/3] chore(setup): make deprecation note mode-aware, fix skill guide, add tests Address Review Council findings on PR #30: - The warning no longer overclaims equivalence. 'dhq skills' has no uninstall and installs at each agent's own default scope, so setupDeprecationNote() now branches: * --uninstall -> says there's no 'dhq skills' uninstall yet and that 'dhq setup --uninstall' stays the removal path; * --project -> notes 'dhq skills' uses the agent's default scope (e.g. Cursor is user-scope there, project-scope here), not project-local; * plain install -> points at 'dhq skills install --agent '. - skills/deployhq/references/auth-setup.md (shipped into agents by 'dhq skills install') led with 'dhq setup' and would teach agents to use the deprecated command. It now leads with 'dhq skills' and marks 'dhq setup' deprecated with the uninstall/scope caveats. - Add internal/commands/setup_test.go: locks the setup->skills name mapping against real registered targets, covers the three message modes, and asserts the warning lands on stderr with stdout left clean. go test ./... -race, go vet, golangci-lint all clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/commands/setup.go | 36 +++++++-- internal/commands/setup_test.go | 97 ++++++++++++++++++++++++ skills/deployhq/references/auth-setup.md | 48 ++++++------ 3 files changed, 152 insertions(+), 29 deletions(-) create mode 100644 internal/commands/setup_test.go diff --git a/internal/commands/setup.go b/internal/commands/setup.go index e230170..b69c38f 100644 --- a/internal/commands/setup.go +++ b/internal/commands/setup.go @@ -129,11 +129,11 @@ func newAgentSetupCmd(a agentSetup) *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { env := cliCtx.Envelope // 'dhq setup' is deprecated in favour of 'dhq skills'. Warn on every - // use (stderr, so JSON/data on stdout is unaffected) and point at the - // equivalent command rather than silently doing the old thing. - env.Warn("'dhq setup' is deprecated; use 'dhq skills install --agent %s' instead "+ - "(auto-detects agents and supports 12 of them). 'dhq setup' will be removed "+ - "in a future release.", a.SkillsName) + // use (stderr, so JSON/data on stdout is unaffected). The note is + // mode-aware: 'dhq skills' has no uninstall and installs at each + // agent's own default scope, so we don't claim a like-for-like + // replacement for the --uninstall or --project paths. + env.Warn("%s", setupDeprecationNote(a, uninstall, project)) sc := scopeUser if project { @@ -164,6 +164,32 @@ func newAgentSetupCmd(a agentSetup) *cobra.Command { return cmd } +// setupDeprecationNote builds the per-run deprecation warning for `dhq setup`. +// It is mode-aware because the successor command isn't a like-for-like drop-in: +// - `dhq skills` has no uninstall, so the --uninstall path keeps using setup; +// - `dhq skills install --agent ` installs at that agent's own default +// scope (e.g. Cursor is user-scope there, project-scope here), so we don't +// promise scope equivalence on the --project path. +func setupDeprecationNote(a agentSetup, uninstall, project bool) string { + switch { + case uninstall: + return fmt.Sprintf( + "'dhq setup' is deprecated and will be removed in a future release. "+ + "'dhq skills' has no uninstall yet, so 'dhq setup %s --uninstall' "+ + "remains the way to remove this integration for now.", a.Use) + case project: + return fmt.Sprintf( + "'dhq setup' is deprecated; prefer 'dhq skills install --agent %s'. "+ + "Note 'dhq skills' installs at that agent's default scope rather than "+ + "project-local, and 'dhq setup' will be removed in a future release.", a.SkillsName) + default: + return fmt.Sprintf( + "'dhq setup' is deprecated; use 'dhq skills install --agent %s' instead "+ + "(auto-detects agents and supports 12 of them). 'dhq setup' will be "+ + "removed in a future release.", a.SkillsName) + } +} + func longHelp(a agentSetup) string { userPath, userErr := a.PathFor(scopeUser) projPath, _ := a.PathFor(scopeProject) diff --git a/internal/commands/setup_test.go b/internal/commands/setup_test.go new file mode 100644 index 0000000..5f9ff45 --- /dev/null +++ b/internal/commands/setup_test.go @@ -0,0 +1,97 @@ +package commands + +import ( + "bytes" + "io" + "strings" + "testing" + + "github.com/deployhq/deployhq-cli/internal/cli" + "github.com/deployhq/deployhq-cli/internal/output" + "github.com/deployhq/deployhq-cli/internal/skillinstaller" +) + +// TestSetupSkillsNamesResolveToTargets locks the setup→skills agent-name +// mapping: every SkillsName the deprecation note points at must resolve to a +// real registered skills target, or the migration hint sends users to a +// nonexistent --agent value. +func TestSetupSkillsNamesResolveToTargets(t *testing.T) { + for _, a := range agents { + if a.SkillsName == "" { + t.Errorf("agent %q has no SkillsName for the deprecation hint", a.Use) + continue + } + if skillinstaller.Find(a.SkillsName) == nil { + t.Errorf("agent %q maps to SkillsName %q, which is not a registered 'dhq skills' target", + a.Use, a.SkillsName) + } + } +} + +// TestSetupDeprecationNote verifies the note is mode-aware: it must not tell +// uninstall users to run an install command, and must not promise scope +// equivalence on the --project path. +func TestSetupDeprecationNote(t *testing.T) { + a := agentSetup{Use: "claude", SkillsName: "claude-code"} + + t.Run("install", func(t *testing.T) { + note := setupDeprecationNote(a, false, false) + mustContain(t, note, "deprecated") + mustContain(t, note, "dhq skills install --agent claude-code") + }) + + t.Run("project", func(t *testing.T) { + note := setupDeprecationNote(a, false, true) + mustContain(t, note, "claude-code") + // Must flag that skills uses its own default scope, not project-local. + mustContain(t, note, "default scope") + }) + + t.Run("uninstall", func(t *testing.T) { + note := setupDeprecationNote(a, true, false) + mustContain(t, note, "no uninstall") + mustContain(t, note, "dhq setup claude --uninstall") + // Must NOT point uninstall users at an install command. + if strings.Contains(note, "install --agent") { + t.Errorf("uninstall note should not suggest an install command: %q", note) + } + }) +} + +// TestSetupCommand_WarnsOnStderrNotStdout drives a real setup subcommand and +// asserts the deprecation warning lands on stderr while stdout stays clean — +// the load-bearing output-contract property of the deprecation. +func TestSetupCommand_WarnsOnStderrNotStdout(t *testing.T) { + t.Setenv("HOME", t.TempDir()) // isolate from the real ~/.claude + + var stdout, stderr bytes.Buffer + origCtx := cliCtx + t.Cleanup(func() { cliCtx = origCtx }) + cliCtx = &cli.Context{ + Envelope: &output.Envelope{Stdout: &stdout, Stderr: &stderr, Logger: output.NewLogger()}, + } + + root := newSetupCmd() + // --uninstall on a fresh HOME removes nothing (no files written), so this + // exercises the warning path without filesystem side effects. + root.SetArgs([]string{"claude", "--uninstall"}) + root.SetOut(io.Discard) + root.SetErr(io.Discard) + if err := root.Execute(); err != nil { + t.Fatalf("execute: %v", err) + } + + if !strings.Contains(stderr.String(), "deprecated") { + t.Errorf("deprecation warning missing from stderr: %q", stderr.String()) + } + if strings.TrimSpace(stdout.String()) != "" { + t.Errorf("stdout must stay clean (data channel), got: %q", stdout.String()) + } +} + +func mustContain(t *testing.T, s, sub string) { + t.Helper() + if !strings.Contains(s, sub) { + t.Errorf("expected %q to contain %q", s, sub) + } +} diff --git a/skills/deployhq/references/auth-setup.md b/skills/deployhq/references/auth-setup.md index 677f2f4..168f6df 100644 --- a/skills/deployhq/references/auth-setup.md +++ b/skills/deployhq/references/auth-setup.md @@ -110,39 +110,39 @@ account = "mycompany" ## Agent Setup -### `dhq setup claude` -Install Claude Code integration files. +### `dhq skills` (preferred) -| Flag | Description | -|------|-------------| -| `--project` | Install to project directory (`.claude/`) instead of user (`~/.claude/`) | -| `--uninstall` | Remove integration files | - -```bash -dhq setup claude -dhq setup claude --project -dhq setup claude --uninstall -``` - -### `dhq setup codex` -Install OpenAI Codex integration. +Install the DeployHQ skill into the AI coding agents on this machine. `dhq skills` +auto-detects installed agents and supports 12 of them (Aider, Antigravity, Claude +Code, Cline, Codex CLI, Continue.dev, Cursor, Gemini CLI, GitHub Copilot, Kiro CLI, +OpenCode, Windsurf). ```bash -dhq setup codex +dhq skills list # detected agents + skill status +dhq skills install # install for detected user-scope agents +dhq skills install --agent claude-code # install for a specific agent +dhq skills install --agent copilot # project-scope agents are opt-in via --agent ``` -### `dhq setup cursor` -Install Cursor integration. +User-scope agents install into your home directory and are covered by the bare +`dhq skills install`; project-scope agents write into the current repository and +require an explicit `--agent` flag. -```bash -dhq setup cursor -``` +### `dhq setup` (deprecated) -### `dhq setup windsurf` -Install Windsurf integration. +> **Deprecated:** `dhq setup claude|codex|cursor|windsurf` is the older, narrower +> predecessor of `dhq skills install`. It still works but warns on use and will be +> removed in a future release. Prefer `dhq skills`. Two caveats when migrating: +> `dhq skills` has no uninstall yet (use `dhq setup --uninstall` to remove), +> and it installs at each agent's own default scope, which may differ from +> `dhq setup --project`. ```bash -dhq setup windsurf +dhq setup claude # ~ dhq skills install --agent claude-code +dhq setup codex # ~ dhq skills install --agent codex +dhq setup cursor # ~ dhq skills install --agent cursor +dhq setup windsurf # ~ dhq skills install --agent windsurf +dhq setup claude --uninstall # remove (no dhq skills equivalent yet) ``` ### `dhq mcp` From 91c58a8747ba2e3d8d8f18ceba9371b728a96de7 Mon Sep 17 00:00:00 2001 From: Facundo Farias Date: Wed, 1 Jul 2026 11:47:57 +0200 Subject: [PATCH 3/3] chore(setup): point deprecation short help at 'dhq skills install' CodeRabbit review on PR #30: the parent command's short help said 'use dhq skills', but the actual replacement is the installing command. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/commands/setup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/commands/setup.go b/internal/commands/setup.go index b69c38f..a0cc588 100644 --- a/internal/commands/setup.go +++ b/internal/commands/setup.go @@ -104,7 +104,7 @@ var agents = []agentSetup{ func newSetupCmd() *cobra.Command { cmd := &cobra.Command{ Use: "setup", - Short: "Install agent plugins (deprecated — use 'dhq skills')", + Short: "Install agent plugins (deprecated — use 'dhq skills install')", Long: "Install DeployHQ agent integration files for AI coding assistants.\n\n" + "DEPRECATED: 'dhq setup' is superseded by 'dhq skills install', which\n" + "auto-detects installed agents and supports 12 of them (vs the 4 here).\n" +