[PPSC-836] feat(install): add interactive MCP install wizard with hooks#199
Open
yiftach-armis wants to merge 16 commits into
Open
[PPSC-836] feat(install): add interactive MCP install wizard with hooks#199yiftach-armis wants to merge 16 commits into
yiftach-armis wants to merge 16 commits into
Conversation
…dation Add interactive installation flow, theme selection, hook management (native Git hooks and pre-commit framework), and plugin validation.
- Quote paths in hook command builders to handle spaces in usernames - Add newline validation to WriteEnvFromValues preventing .env injection - Use readJSONFileAsMapSafe in native_hooks.go to surface parse errors - Call RemoveNativeHook during uninstall to clean up hook configs
- Fix exit code swallowed by head -c 0 pipe (use >/dev/null 2>&1) - Add posixQuote helper for safe shell path escaping - Fix RemovePreCommit end-marker search to start from start-marker - Validate repoRoot as absolute path with .git directory (CWE-73) - Validate APPDATA env var as absolute path (CWE-73) - Fix error wrapping in validate.go (%s -> %w) - Handle backup rename error in WriteEnvFromValues - Guard lipgloss styling when colors disabled (accessible mode) - Fix allCovered logic to only skip pre-commit when Claude is sole tool - Download shared plugin when hooks need it (not just editors) - Handle WriteManifest error instead of discarding it - Update hook_init help text to reflect both hook modes - Fix gosec G117 lint in validate_test.go - Skip executable bit check on Windows in precommit_test.go
- validate.go: only show credential help text for 401 errors, not network/5xx failures - precommit.go: support git worktrees/submodules by resolving hooks dir via git rev-parse with fallback to .git file parsing - cli/interactive.go: check stderr TTY too (needed for TUI rendering) - hook.go: fix misleading "automatically" wording in help text - hooks.go: tighten isArmisHookCommand to match "armis-cli scan repo" specifically; add 10MB file size guard before reading settings - native_hooks.go: tighten isArmisHookJSON to match specific adapter filenames; validate pluginDir is absolute in InstallNativeHook - plugin.go: use os.CreateTemp for atomic writes (randomized temp name) - install_interactive.go: pass accessible flag to validateAndReport for plain-text markers when colors disabled
The tmpPath variable from os.CreateTemp is in a known safe directory (plugin dir). Add nolint:gosec directives on os.Remove calls.
- Centralize productionBaseURL in auth package, reuse in cmd and install - validate.go: suppress false-positive CWE-295 (debug param != TLS skip) - hooks.go: suppress CWE-22 on os.UserHomeDir (trusted OS source) - native_hooks.go: suppress CWE-73 on APPDATA (validated as absolute) - plugin.go: abort if backup rename fails (prevent data loss); suppress CWE-22 on final rename (known plugin dir path) - precommit.go: return resolveHooksDir errors instead of swallowing (only treat missing .git as no-op) - uninstall.go: report native hook removal errors as warnings - install_interactive.go: remove unused pluginDir param from offerHookSetup
…scanner findings Add //nolint:gosec on the os.Rename taint-analysis false positive (plugin.go:431) that failed CI lint, and add armis:ignore comments for the remaining CWE-73/CWE-22 security scanner findings on precommit.go and native_hooks.go.
… scanner findings Suppress false-positive findings from the Armis Security Scanner: - CWE-78 on precommit.go:206 (posixQuote escapes shell metacharacters) - CWE-22 on native_hooks.go:124 (pluginDir validated absolute + hardcoded segments) - CWE-73 on precommit.go:29 (repoRoot validated absolute + hardcoded ".git")
- readJSONFileAsMapSafe: use operation-agnostic error message - WriteEnvFromValues: copy .env to .bak instead of rename for rollback safety - install command: add explicit --interactive flag to match PR description - hook_init: handle flag parsing errors instead of ignoring them - collectCredentials: detect huh.ErrUserAborted and cancel wizard on Ctrl+C
Suppress CWE-522, CWE-78, CWE-73 on WriteEnvFromValues, copyFile, and buildCursorHooks — all paths from known install locations, not user input.
…improve credential validation - Extract brandWarn color constant to eliminate duplicate inline definitions - Mark --interactive and --non-interactive flags as mutually exclusive via Cobra - Fix hook coverage logic: allCovered when ANY AI tool has native hooks (not only Claude alone) - Return empty credentials on validation failure so .env is not written with bad values - Add ARMIS_API_URL/ARMIS_REGION env var support to credential validation endpoint - Remove unused HookConfigFormat function (dead code after manifest tracking refactor)
…ings Add targeted armis:ignore comments for CWE-78, CWE-918, CWE-22, CWE-59, CWE-347, CWE-502, CWE-522, CWE-312, CWE-253, and CWE-367 findings that were not suppressed by existing annotations due to line-number mismatch or missing CWE specificity.
Test Coverage Reporttotal: (statements) 72.2% Coverage by function |
There was a problem hiding this comment.
Pull request overview
Adds an interactive armis-cli install --interactive wizard to simplify MCP server installation across supported editors, including optional security scanning hooks (native client hooks + git pre-commit) and credential validation. This fits into the CLI’s existing installer/uninstaller flow by automating config file updates and adding guardrails (validation, atomic writes, backups).
Changes:
- Introduces an interactive TUI install flow (theme/accessibility-aware) with credential collection + validation and editor selection.
- Adds hook management: Claude settings hooks, native hook configs for multiple AI clients, and repo-level git pre-commit hook install/remove logic.
- Hardens install/uninstall plumbing with atomic file writes, backups, config parsing safety, and shared production base URL centralization.
Reviewed changes
Copilot reviewed 24 out of 25 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/install.sh | Adds additional scanner suppression annotation for installer verification/move logic. |
| scripts/install.ps1 | Adds scanner suppression annotations around archive extraction paths. |
| internal/scan/image/image.go | Adds suppression annotations for docker pull/save command execution. |
| internal/install/validate.go | New credential validation helper (supports ARMIS_API_URL/ARMIS_REGION). |
| internal/install/validate_test.go | Unit tests for credential validation success/failure modes. |
| internal/install/precommit.go | New git pre-commit hook installer/remover with worktree/submodule support. |
| internal/install/precommit_test.go | Unit tests covering pre-commit install/remove/idempotency/fallback behavior. |
| internal/install/plugin.go | Adds .env writer with backup + atomic write semantics. |
| internal/install/plugin_test.go | Unit tests for .env writing, backup creation, and permissions. |
| internal/install/native_hooks.go | New native hook client detection + config install/remove for multiple tools. |
| internal/install/native_hooks_test.go | Unit tests for native hook install/remove/merge/idempotency and detection. |
| internal/install/hooks.go | New Claude Code settings hook install/remove (with file size sanity guard). |
| internal/install/hooks_test.go | Unit tests for Claude hook install/remove/idempotency and JSON parse errors. |
| internal/httpclient/client.go | Adds suppression annotation for an upstream-validated URL error branch. |
| internal/cmd/uninstall.go | Enhances uninstall output styling and adds native hook removal for full uninstall. |
| internal/cmd/root.go | Uses auth.ProductionBaseURL instead of duplicating the production URL constant. |
| internal/cmd/install.go | Adds --interactive / --non-interactive flags and routes to wizard when appropriate. |
| internal/cmd/install_theme.go | Adds a custom huh theme and disables it when colors are disabled. |
| internal/cmd/install_interactive.go | New interactive install wizard logic: credentials, editors, hooks, manifest updates. |
| internal/cmd/hook.go | Adds armis-cli hook command group for hook management. |
| internal/cmd/hook_init.go | Adds armis-cli hook init to install/remove repo pre-commit hook. |
| internal/cli/interactive.go | Adds cli.IsInteractive() helper (stdin+stderr TTY detection). |
| internal/auth/client.go | Adds ProductionBaseURL constant and additional scanner suppressions. |
| go.mod | Adds github.com/charmbracelet/huh dependency (and related indirect deps). |
| go.sum | Updates checksums for newly added/updated dependencies. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| data["version"] = 1 | ||
| } | ||
|
|
||
| hooks := buildCursorHooks(pluginDir).(map[string]interface{}) |
| data["version"] = 1 | ||
| } | ||
|
|
||
| hooks := buildCopilotHooks(pluginDir).(map[string]interface{}) |
| return "", "", true, false | ||
| } | ||
| if err := validateAndReport(clientID, clientSecret, accessible); err != nil { | ||
| fmt.Fprintln(os.Stderr, " Proceeding without credential validation. You can fix the .env file later.") |
Comment on lines
224
to
229
| case targetCopilot: | ||
| if err := u.DeregisterEditor(install.EditorVSCode); err != nil { | ||
| fmt.Fprintf(os.Stderr, " ✗ VS Code: %v\n", err) | ||
| printFail(fmt.Sprintf("VS Code: %v", err)) | ||
| } else { | ||
| fmt.Fprintf(os.Stderr, " ✓ VS Code\n") | ||
| printSuccess("VS Code") | ||
| } |
Comment on lines
238
to
249
| default: | ||
| id := install.EditorID(name) | ||
| e, ok := install.EditorByID(id) | ||
| if !ok { | ||
| fmt.Fprintf(os.Stderr, " ✗ Unknown editor: %s\n", name) | ||
| printFail(fmt.Sprintf("Unknown editor: %s", name)) | ||
| continue | ||
| } | ||
| if err := u.DeregisterEditor(id); err != nil { | ||
| fmt.Fprintf(os.Stderr, " ✗ %s: %v\n", e.Name, err) | ||
| printFail(fmt.Sprintf("%s: %v", e.Name, err)) | ||
| } else { | ||
| fmt.Fprintf(os.Stderr, " ✓ %s\n", e.Name) | ||
| printSuccess(e.Name) | ||
| } |
- Replace unchecked type assertions with concrete return types for all buildHooks functions, eliminating potential panics - Fix misleading credential skip message to clarify credentials are not saved - Add native hook removal to per-target uninstall so editor hook configs are cleaned up alongside MCP deregistration
Comment on lines
+429
to
+434
| closed = true | ||
| // armis:ignore cwe:22 reason:cleanPath derived from known plugin dir + ".env", not external input | ||
| if err := os.Rename(tmpPath, cleanPath); err != nil { //nolint:gosec // G703: both paths from known plugin dir | ||
| _ = os.Remove(tmpPath) //nolint:gosec // G703: tmpPath from os.CreateTemp in known plugin dir | ||
| return fmt.Errorf("finalizing env file: %w", err) | ||
| } |
| gitEntry := filepath.Join(repoRoot, ".git") | ||
| // armis:ignore cwe:73 reason:repoRoot validated as absolute path above; .git is a hardcoded segment | ||
| if _, err := os.Stat(gitEntry); err != nil { | ||
| return fmt.Errorf("not a git repository (no .git): %s", repoRoot) |
… write - Distinguish ENOENT from other stat errors when checking .git directory - Handle Windows os.Rename limitation by retrying after removing destination
The install wizard sets up MCP integrations, not the CLI scanner. The 'armis-cli scan repo .' suggestion was unrelated to the install flow.
| // armis:ignore cwe:73 reason:bakPath derived from cleanPath which is constructed from known plugin dir + ".env" | ||
| if err := copyFile(cleanPath, bakPath); err != nil { | ||
| return fmt.Errorf("could not back up %s: %w", filepath.Base(cleanPath), err) | ||
| } |
Comment on lines
+431
to
+437
| if err := os.Rename(tmpPath, cleanPath); err != nil { //nolint:gosec // G703: both paths from known plugin dir | ||
| // On Windows, Rename fails if destination exists; remove and retry. | ||
| _ = os.Remove(cleanPath) //nolint:gosec // G703: cleanPath from known plugin dir; already backed up above | ||
| if retryErr := os.Rename(tmpPath, cleanPath); retryErr != nil { | ||
| _ = os.Remove(tmpPath) //nolint:gosec // G703: tmpPath from os.CreateTemp in known plugin dir | ||
| return fmt.Errorf("finalizing env file: %w", retryErr) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related Issue
Type of Change
Problem
Installing the Armis CLI MCP plugin required users to manually edit JSON configuration files for Claude Code, Cursor, and Windsurf — error-prone and undocumented.
Solution
Added an interactive install wizard (
armis-cli install --interactive) powered by charmbracelet/huh that guides users through IDE selection, validates existing configurations, and safely writes MCP plugin entries. Includes:armis-cli scan repoautomaticallyTesting
Automated Tests
Manual Testing
Ran
armis-cli install --interactiveagainst fresh and pre-configured Claude/Cursor/Windsurf setups; verified hook installation with both native git hooks and pre-commit configs.Reviewer Notes
internal/api/client_test.goandinternal/auth/client.go) — not introduced by this PRcharmbracelet/huhdependency is added for the interactive TUI formsChecklist