@esoteric-logic/praxis-harness 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +192 -0
  3. package/base/CLAUDE.md +148 -0
  4. package/base/commands/context-reset.md +72 -0
  5. package/base/commands/debug.md +63 -0
  6. package/base/commands/discover.md +49 -0
  7. package/base/commands/gsd-discuss.md +53 -0
  8. package/base/commands/gsd-execute.md +60 -0
  9. package/base/commands/gsd-verify.md +78 -0
  10. package/base/commands/kit.md +62 -0
  11. package/base/commands/plan.md +91 -0
  12. package/base/commands/ralph.md +110 -0
  13. package/base/commands/review.md +81 -0
  14. package/base/commands/risk.md +53 -0
  15. package/base/commands/ship.md +74 -0
  16. package/base/commands/spec.md +121 -0
  17. package/base/commands/standup.md +57 -0
  18. package/base/rules/architecture.md +51 -0
  19. package/base/rules/azure.md +90 -0
  20. package/base/rules/code-quality.md +65 -0
  21. package/base/rules/coding.md +139 -0
  22. package/base/rules/communication.md +69 -0
  23. package/base/rules/context-management.md +136 -0
  24. package/base/rules/execution-loop.md +84 -0
  25. package/base/rules/git-workflow.md +51 -0
  26. package/base/rules/github-actions.md +48 -0
  27. package/base/rules/powershell.md +72 -0
  28. package/base/rules/profile.md +31 -0
  29. package/base/rules/security.md +40 -0
  30. package/base/rules/terraform.md +48 -0
  31. package/base/rules/vault.md +134 -0
  32. package/base/skills/code-gc/SKILL.md +205 -0
  33. package/base/skills/code-simplifier/SKILL.md +132 -0
  34. package/base/skills/prd-writer/SKILL.md +108 -0
  35. package/base/skills/prd-writer/references/prd-template.md +22 -0
  36. package/base/skills/pre-commit-lint/SKILL.md +71 -0
  37. package/base/skills/scaffold-exist/SKILL.md +85 -0
  38. package/base/skills/scaffold-new/SKILL.md +177 -0
  39. package/base/skills/scaffold-new/references/claude-progress-template.json +24 -0
  40. package/base/skills/scaffold-new/references/gitignore-template.txt +65 -0
  41. package/base/skills/scaffold-new/references/repo-CLAUDE-md-template.md +87 -0
  42. package/base/skills/scaffold-new/references/vault-index-template.md +31 -0
  43. package/base/skills/scaffold-new/references/vault-learnings-template.md +21 -0
  44. package/base/skills/scaffold-new/references/vault-status-template.md +21 -0
  45. package/base/skills/scaffold-new/references/vault-tasks-template.md +20 -0
  46. package/base/skills/session-retro/SKILL.md +146 -0
  47. package/base/skills/subagent-review/SKILL.md +126 -0
  48. package/base/skills/vault-gc/SKILL.md +93 -0
  49. package/base/skills/verify-app/SKILL.md +156 -0
  50. package/bin/praxis.js +385 -0
  51. package/kits/infrastructure/KIT.md +66 -0
  52. package/kits/infrastructure/commands/infra-apply.md +44 -0
  53. package/kits/infrastructure/commands/infra-compliance.md +65 -0
  54. package/kits/infrastructure/commands/infra-drift.md +45 -0
  55. package/kits/infrastructure/commands/infra-plan.md +45 -0
  56. package/kits/infrastructure/install.sh +43 -0
  57. package/kits/infrastructure/rules/infrastructure.md +82 -0
  58. package/kits/infrastructure/teardown.sh +14 -0
  59. package/kits/web-designer/KIT.md +76 -0
  60. package/kits/web-designer/commands/web-audit.md +67 -0
  61. package/kits/web-designer/commands/web-component.md +54 -0
  62. package/kits/web-designer/commands/web-init.md +42 -0
  63. package/kits/web-designer/commands/web-tokens-sync.md +49 -0
  64. package/kits/web-designer/install.sh +41 -0
  65. package/kits/web-designer/rules/web-design.md +79 -0
  66. package/kits/web-designer/teardown.sh +26 -0
  67. package/package.json +28 -0
  68. package/scripts/health-check.sh +160 -0
  69. package/scripts/lint-harness.sh +195 -0
  70. package/scripts/onboard-mcp.sh +326 -0
  71. package/scripts/update.sh +88 -0
  72. package/templates/_index.md +33 -0
  73. package/templates/adr.md +28 -0
  74. package/templates/claude-progress.json +24 -0
  75. package/templates/plan.md +46 -0
  76. package/templates/project-index.md +31 -0
  77. package/templates/session-note.md +21 -0
  78. package/templates/status.md +27 -0
  79. package/templates/tasks.md +27 -0
@@ -0,0 +1,160 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # ════════════════════════════════════════════════════════════════
5
+ # Praxis — Health Check
6
+ # Verifies install integrity: symlinks, config, tools, base layer
7
+ # ════════════════════════════════════════════════════════════════
8
+
9
+ CLAUDE_DIR="$HOME/.claude"
10
+ CONFIG_FILE="$CLAUDE_DIR/praxis.config.json"
11
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12
+ PRAXIS_DIR="$(dirname "$SCRIPT_DIR")"
13
+
14
+ PASS=0
15
+ FAIL=0
16
+ TOTAL=0
17
+
18
+ check() {
19
+ TOTAL=$((TOTAL + 1))
20
+ if eval "$1" 2>/dev/null; then
21
+ echo " ✓ $2"
22
+ PASS=$((PASS + 1))
23
+ else
24
+ echo " ✗ $2"
25
+ FAIL=$((FAIL + 1))
26
+ fi
27
+ }
28
+
29
+ echo "Praxis Health Check"
30
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
31
+
32
+ # ─── CLAUDE.md symlink ───
33
+ echo ""
34
+ echo "Core:"
35
+ check "[[ -e '$CLAUDE_DIR/CLAUDE.md' ]]" "CLAUDE.md installed"
36
+
37
+ # ─── Rules symlinks ───
38
+ echo ""
39
+ echo "Rules:"
40
+ if [[ -d "$PRAXIS_DIR/base/rules" ]]; then
41
+ for rule in "$PRAXIS_DIR"/base/rules/*.md; do
42
+ [[ -f "$rule" ]] || continue
43
+ fname=$(basename "$rule")
44
+ check "[[ -e '$CLAUDE_DIR/rules/$fname' ]]" "rules/$fname installed"
45
+ done
46
+ fi
47
+
48
+ # ─── Commands symlinks ───
49
+ echo ""
50
+ echo "Commands:"
51
+ if [[ -d "$PRAXIS_DIR/base/commands" ]]; then
52
+ for cmd in "$PRAXIS_DIR"/base/commands/*.md; do
53
+ [[ -f "$cmd" ]] || continue
54
+ fname=$(basename "$cmd")
55
+ check "[[ -e '$CLAUDE_DIR/commands/$fname' ]]" "commands/$fname installed"
56
+ done
57
+ fi
58
+
59
+ # ─── Skills symlinks ───
60
+ echo ""
61
+ echo "Skills:"
62
+ if [[ -d "$PRAXIS_DIR/base/skills" ]]; then
63
+ for skill_dir in "$PRAXIS_DIR"/base/skills/*/; do
64
+ [[ -d "$skill_dir" ]] || continue
65
+ skill_name=$(basename "$skill_dir")
66
+ check "[[ -e '$CLAUDE_DIR/skills/$skill_name' ]]" "skills/$skill_name installed"
67
+ done
68
+ fi
69
+
70
+ # ─── Kits symlink ───
71
+ echo ""
72
+ echo "Kits:"
73
+ check "[[ -e '$CLAUDE_DIR/kits' ]]" "kits directory installed"
74
+
75
+ # ─── Config ───
76
+ echo ""
77
+ echo "Config:"
78
+ check "[[ -f '$CONFIG_FILE' ]]" "praxis.config.json exists"
79
+
80
+ if [[ -f "$CONFIG_FILE" ]]; then
81
+ VAULT_PATH=$(jq -r '.vault_path // empty' "$CONFIG_FILE" 2>/dev/null)
82
+ if [[ -n "$VAULT_PATH" ]]; then
83
+ check "[[ -d '$VAULT_PATH' ]]" "vault_path ($VAULT_PATH) is a real directory"
84
+ else
85
+ TOTAL=$((TOTAL + 1))
86
+ echo " ✗ vault_path not set in config"
87
+ FAIL=$((FAIL + 1))
88
+ fi
89
+ fi
90
+
91
+ # ─── Required tools (conditional on backend) ───
92
+ echo ""
93
+ echo "Tools:"
94
+ VAULT_BACKEND=""
95
+ if [[ -f "$CONFIG_FILE" ]]; then
96
+ VAULT_BACKEND=$(jq -r '.vault_backend // "obsidian"' "$CONFIG_FILE" 2>/dev/null)
97
+ fi
98
+ if [[ "$VAULT_BACKEND" == "obsidian" ]]; then
99
+ check "command -v obsidian" "Obsidian CLI available"
100
+ else
101
+ check "command -v rg" "ripgrep available"
102
+ fi
103
+ check "command -v node" "node available"
104
+ check "command -v claude" "claude available"
105
+ check "command -v jq" "jq available"
106
+
107
+ # ─── MCP Servers (warn only) ───
108
+ echo ""
109
+ echo "MCP Servers:"
110
+ if command -v claude &>/dev/null; then
111
+ MCP_LIST=$(claude mcp list 2>/dev/null || true)
112
+ for server in context7 perplexity github; do
113
+ TOTAL=$((TOTAL + 1))
114
+ if echo "$MCP_LIST" | grep -q "$server"; then
115
+ echo " ✓ $server registered"
116
+ PASS=$((PASS + 1))
117
+ else
118
+ echo " ⚠ $server not registered (optional)"
119
+ PASS=$((PASS + 1)) # optional = pass either way
120
+ fi
121
+ done
122
+ else
123
+ echo " ⚠ claude CLI not available — cannot check MCP servers"
124
+ fi
125
+
126
+ # ─── Broken symlinks (relevant for git-clone/symlink installs) ───
127
+ echo ""
128
+ echo "Symlink integrity:"
129
+ BROKEN=0
130
+ for dir in "$CLAUDE_DIR/rules" "$CLAUDE_DIR/commands" "$CLAUDE_DIR/skills"; do
131
+ if [[ -d "$dir" ]]; then
132
+ while IFS= read -r link; do
133
+ if [[ -L "$link" && ! -e "$link" ]]; then
134
+ echo " ✗ Broken symlink: $link"
135
+ BROKEN=$((BROKEN + 1))
136
+ fi
137
+ done < <(find "$dir" -maxdepth 1 -type l 2>/dev/null)
138
+ fi
139
+ done
140
+
141
+ TOTAL=$((TOTAL + 1))
142
+ if [[ $BROKEN -eq 0 ]]; then
143
+ echo " ✓ No broken symlinks"
144
+ PASS=$((PASS + 1))
145
+ else
146
+ echo " ✗ $BROKEN broken symlink(s) found"
147
+ FAIL=$((FAIL + 1))
148
+ fi
149
+
150
+ # ─── Summary ───
151
+ echo ""
152
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
153
+ echo " Results: $PASS/$TOTAL passed"
154
+ if [[ $FAIL -gt 0 ]]; then
155
+ echo " $FAIL check(s) failed"
156
+ exit 1
157
+ else
158
+ echo " All checks passed"
159
+ exit 0
160
+ fi
@@ -0,0 +1,195 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # ════════════════════════════════════════════════════════════════
5
+ # Praxis — Harness Content Lint
6
+ # Validates frontmatter, placeholders, registry consistency, syntax
7
+ # ════════════════════════════════════════════════════════════════
8
+
9
+ REPO_PATH="${1:-$(cd "$(dirname "$0")/.." && pwd)}"
10
+
11
+ ERRORS=0
12
+ WARNINGS=0
13
+
14
+ error() {
15
+ echo " ✗ ERROR: $1"
16
+ ERRORS=$((ERRORS + 1))
17
+ }
18
+
19
+ warn() {
20
+ echo " ⚠ WARN: $1"
21
+ WARNINGS=$((WARNINGS + 1))
22
+ }
23
+
24
+ ok() {
25
+ echo " ✓ $1"
26
+ }
27
+
28
+ echo "Praxis Harness Lint"
29
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
30
+ echo " Repo: $REPO_PATH"
31
+
32
+ # ─── 1. Command frontmatter ───
33
+ echo ""
34
+ echo "Commands (description: field):"
35
+ if [[ -d "$REPO_PATH/base/commands" ]]; then
36
+ for cmd in "$REPO_PATH"/base/commands/*.md; do
37
+ [[ -f "$cmd" ]] || continue
38
+ fname=$(basename "$cmd")
39
+ if head -5 "$cmd" | grep -q "^description:"; then
40
+ ok "$fname"
41
+ else
42
+ error "$fname missing description: in frontmatter"
43
+ fi
44
+ done
45
+ fi
46
+
47
+ # ─── 2. Skill frontmatter ───
48
+ echo ""
49
+ echo "Skills (name:, disable-model-invocation:, description:):"
50
+ if [[ -d "$REPO_PATH/base/skills" ]]; then
51
+ for skill_dir in "$REPO_PATH"/base/skills/*/; do
52
+ [[ -d "$skill_dir" ]] || continue
53
+ skill_name=$(basename "$skill_dir")
54
+ skill_file="$skill_dir/SKILL.md"
55
+ if [[ ! -f "$skill_file" ]]; then
56
+ error "skills/$skill_name/ missing SKILL.md"
57
+ continue
58
+ fi
59
+ # Check required frontmatter fields (within first 10 lines)
60
+ header=$(head -10 "$skill_file")
61
+ missing=""
62
+ echo "$header" | grep -q "^name:" || missing="$missing name:"
63
+ echo "$header" | grep -q "^disable-model-invocation:" || missing="$missing disable-model-invocation:"
64
+ echo "$header" | grep -q "^description:" || missing="$missing description:"
65
+ if [[ -z "$missing" ]]; then
66
+ ok "skills/$skill_name"
67
+ else
68
+ error "skills/$skill_name SKILL.md missing:$missing"
69
+ fi
70
+ done
71
+ fi
72
+
73
+ # ─── 3. Kit frontmatter ───
74
+ echo ""
75
+ echo "Kits (name:, version:, description:, activation:, skills_chain:):"
76
+ if [[ -d "$REPO_PATH/kits" ]]; then
77
+ for kit_dir in "$REPO_PATH"/kits/*/; do
78
+ [[ -d "$kit_dir" ]] || continue
79
+ kit_name=$(basename "$kit_dir")
80
+ kit_file="$kit_dir/KIT.md"
81
+ if [[ ! -f "$kit_file" ]]; then
82
+ error "kits/$kit_name/ missing KIT.md"
83
+ continue
84
+ fi
85
+ header=$(head -15 "$kit_file")
86
+ missing=""
87
+ echo "$header" | grep -q "^name:" || missing="$missing name:"
88
+ echo "$header" | grep -q "^version:" || missing="$missing version:"
89
+ echo "$header" | grep -q "^description:" || missing="$missing description:"
90
+ echo "$header" | grep -q "^activation:" || missing="$missing activation:"
91
+ echo "$header" | grep -q "^skills_chain:" || missing="$missing skills_chain:"
92
+ if [[ -z "$missing" ]]; then
93
+ ok "kits/$kit_name"
94
+ else
95
+ error "kits/$kit_name KIT.md missing:$missing"
96
+ fi
97
+ done
98
+ fi
99
+
100
+ # ─── 4. Placeholder scan ───
101
+ echo ""
102
+ echo "Placeholder scan ({placeholder} patterns):"
103
+ PLACEHOLDER_FOUND=0
104
+ # Scan base/, kits/, docs/, scripts/ — exclude templates/ and */references/
105
+ # Also exclude HTML comments and fenced code blocks
106
+ while IFS= read -r file; do
107
+ [[ -f "$file" ]] || continue
108
+ # Skip templates directory and references directories
109
+ [[ "$file" == *"/templates/"* ]] && continue
110
+ [[ "$file" == *"/references/"* ]] && continue
111
+
112
+ # Strip fenced code blocks (including indented), HTML comments,
113
+ # lines with inline backticks, and shell comments/echo lines
114
+ matches=$(sed -E \
115
+ -e '/^[[:space:]]*```/,/^[[:space:]]*```/d' \
116
+ -e '/<!--/,/-->/d' \
117
+ "$file" \
118
+ | grep -nE '\{[a-zA-Z_][a-zA-Z0-9_-]*\}' \
119
+ | grep -vE '`[^`]*\{[^}]+\}[^`]*`' \
120
+ | grep -vE '^[0-9]+:\s*#' \
121
+ | grep -vE '^[0-9]+:\s*echo ' \
122
+ | grep -vE '\{vault_path\}|\{today_date\}|\{project-slug\}|\{YYYY-MM-DD\}|\{kebab-title\}|\{date\}|\{[nN]\}|\{1-line\}|\{ISO timestamp\}|\{repo_root\}|\{identity_email\}|\{stack\}|\{placeholder\}|\{placeholders\}|\{http_code\}' \
123
+ || true)
124
+ if [[ -n "$matches" ]]; then
125
+ rel_path="${file#"$REPO_PATH"/}"
126
+ while IFS= read -r match; do
127
+ error "$rel_path:$match"
128
+ PLACEHOLDER_FOUND=$((PLACEHOLDER_FOUND + 1))
129
+ done <<< "$matches"
130
+ fi
131
+ done < <(find "$REPO_PATH/base" "$REPO_PATH/docs" "$REPO_PATH/scripts" -name "*.md" -o -name "*.sh" 2>/dev/null; find "$REPO_PATH/kits" -name "*.md" -o -name "*.sh" 2>/dev/null)
132
+
133
+ if [[ $PLACEHOLDER_FOUND -eq 0 ]]; then
134
+ ok "No unreplaced placeholders found"
135
+ fi
136
+
137
+ # ─── 5. Rules registry consistency ───
138
+ echo ""
139
+ echo "Rules registry (CLAUDE.md references vs disk):"
140
+ if [[ -f "$REPO_PATH/base/CLAUDE.md" ]]; then
141
+ # Extract rule filenames from CLAUDE.md registry tables
142
+ rule_refs=$(grep -oE '~/.claude/rules/[a-zA-Z0-9_-]+\.md' "$REPO_PATH/base/CLAUDE.md" \
143
+ | sed 's|~/.claude/rules/||' | sort -u)
144
+ for rule_file in $rule_refs; do
145
+ if [[ -f "$REPO_PATH/base/rules/$rule_file" ]]; then
146
+ ok "rules/$rule_file exists"
147
+ else
148
+ error "CLAUDE.md references rules/$rule_file but file not found"
149
+ fi
150
+ done
151
+ fi
152
+
153
+ # ─── 6. Shell script syntax ───
154
+ echo ""
155
+ echo "Shell syntax (bash -n):"
156
+ while IFS= read -r script; do
157
+ [[ -f "$script" ]] || continue
158
+ fname="${script#"$REPO_PATH"/}"
159
+ if bash -n "$script" 2>/dev/null; then
160
+ ok "$fname"
161
+ else
162
+ error "$fname has syntax errors"
163
+ fi
164
+ done < <(find "$REPO_PATH" -maxdepth 1 -name "*.sh" 2>/dev/null; find "$REPO_PATH/scripts" -name "*.sh" 2>/dev/null; find "$REPO_PATH/kits" -name "*.sh" 2>/dev/null)
165
+
166
+ # ─── 7. Template content warnings ───
167
+ echo ""
168
+ echo "Template content warnings:"
169
+ for check_file in "$REPO_PATH/base/rules/git-workflow.md" "$REPO_PATH/base/rules/profile.md"; do
170
+ if [[ -f "$check_file" ]]; then
171
+ fname="${check_file#"$REPO_PATH"/}"
172
+ if grep -qiE "you@company\.com|Your Name" "$check_file"; then
173
+ warn "$fname contains uncustomized template values (you@company.com or Your Name)"
174
+ else
175
+ ok "$fname — no template placeholders"
176
+ fi
177
+ fi
178
+ done
179
+
180
+ # ─── Summary ───
181
+ echo ""
182
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
183
+ echo " Errors: $ERRORS"
184
+ echo " Warnings: $WARNINGS"
185
+ if [[ $ERRORS -gt 0 ]]; then
186
+ echo " FAILED — fix errors above"
187
+ exit 1
188
+ else
189
+ if [[ $WARNINGS -gt 0 ]]; then
190
+ echo " PASSED with warnings"
191
+ else
192
+ echo " PASSED — all clean"
193
+ fi
194
+ exit 0
195
+ fi
@@ -0,0 +1,326 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # ════════════════════════════════════════════════════════════════
5
+ # Praxis — MCP Server Onboarding
6
+ # Configures cross-cutting MCP servers for Claude Code.
7
+ # Can be sourced (install.sh) or run standalone.
8
+ #
9
+ # Servers:
10
+ # context7 — live library docs (free, no key)
11
+ # perplexity — AI web search (requires API key)
12
+ # github — repo operations (requires PAT)
13
+ #
14
+ # Usage:
15
+ # bash scripts/onboard-mcp.sh [context7|perplexity|github|all]
16
+ # source scripts/onboard-mcp.sh # then call functions directly
17
+ # ════════════════════════════════════════════════════════════════
18
+
19
+ # ─── Constants (match install.sh when sourced) ───
20
+ CLAUDE_DIR="${CLAUDE_DIR:-$HOME/.claude}"
21
+ CONFIG_FILE="${CONFIG_FILE:-$CLAUDE_DIR/praxis.config.json}"
22
+
23
+ # ─── Colors (safe defaults if not already set) ───
24
+ RED="${RED:-\033[0;31m}"
25
+ GREEN="${GREEN:-\033[0;32m}"
26
+ YELLOW="${YELLOW:-\033[0;33m}"
27
+ CYAN="${CYAN:-\033[0;36m}"
28
+ BOLD="${BOLD:-\033[1m}"
29
+ NC="${NC:-\033[0m}"
30
+
31
+ # ─── Output helpers (no-op if already defined by install.sh) ───
32
+ if ! declare -f ok &>/dev/null; then
33
+ ok() { echo -e " $GREEN✓$NC $1"; }
34
+ fi
35
+ if ! declare -f warn &>/dev/null; then
36
+ warn() { echo -e " $YELLOW⚠$NC $1"; }
37
+ fi
38
+ if ! declare -f fail &>/dev/null; then
39
+ fail() { echo -e " $RED✗$NC $1"; }
40
+ fi
41
+
42
+ # ═══════════════════════════════════════════
43
+ # Utilities
44
+ # ═══════════════════════════════════════════
45
+
46
+ mcp_server_exists() {
47
+ local name="$1"
48
+ claude mcp list 2>/dev/null | grep -q "$name"
49
+ }
50
+
51
+ open_url() {
52
+ local url="$1"
53
+ if [[ "$(uname -s)" == "Darwin" ]]; then
54
+ open "$url" 2>/dev/null || echo " Open: $url"
55
+ elif command -v xdg-open &>/dev/null; then
56
+ xdg-open "$url" 2>/dev/null || echo " Open: $url"
57
+ else
58
+ echo " Open: $url"
59
+ fi
60
+ }
61
+
62
+ update_mcp_status() {
63
+ local key="$1"
64
+ local val="$2"
65
+ if [[ -f "$CONFIG_FILE" ]] && command -v jq &>/dev/null; then
66
+ local tmp="$CONFIG_FILE.tmp"
67
+ jq --arg k "$key" --arg v "$val" '.[$k] = $v' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE"
68
+ fi
69
+ }
70
+
71
+ # ═══════════════════════════════════════════
72
+ # Server: Context7 — live library docs
73
+ # ═══════════════════════════════════════════
74
+
75
+ onboard_context7() {
76
+ echo -e " ${BOLD}Context7${NC} — live library/API documentation"
77
+
78
+ # Check if running as Claude Code plugin (settings.json)
79
+ local settings_file="$CLAUDE_DIR/settings.json"
80
+ if [[ -f "$settings_file" ]] && command -v jq &>/dev/null; then
81
+ if jq -e '.enabledPlugins // [] | map(select(contains("context7"))) | length > 0' "$settings_file" &>/dev/null; then
82
+ ok "Context7 already active as Claude Code plugin (skipping MCP)"
83
+ update_mcp_status "context7_status" "plugin"
84
+ return 0
85
+ fi
86
+ fi
87
+
88
+ # Check if already registered as MCP server
89
+ if mcp_server_exists "context7"; then
90
+ ok "Context7 MCP already registered"
91
+ update_mcp_status "context7_status" "configured"
92
+ return 0
93
+ fi
94
+
95
+ # Configure
96
+ echo " Registering Context7 MCP server..."
97
+ if claude mcp add context7 -s user -- npx -y @upstash/context7-mcp 2>/dev/null; then
98
+ # Verify
99
+ if mcp_server_exists "context7"; then
100
+ ok "Context7 MCP registered"
101
+ update_mcp_status "context7_status" "configured"
102
+ else
103
+ warn "Context7 registered but not showing in mcp list"
104
+ update_mcp_status "context7_status" "unverified"
105
+ fi
106
+ else
107
+ fail "Context7 registration failed"
108
+ echo " Run manually: claude mcp add context7 -s user -- npx -y @upstash/context7-mcp"
109
+ update_mcp_status "context7_status" "failed"
110
+ return 1
111
+ fi
112
+ }
113
+
114
+ # ═══════════════════════════════════════════
115
+ # Server: Perplexity — AI web search
116
+ # ═══════════════════════════════════════════
117
+
118
+ onboard_perplexity() {
119
+ echo -e " ${BOLD}Perplexity${NC} — AI-powered web search"
120
+
121
+ # Check if already registered
122
+ if mcp_server_exists "perplexity"; then
123
+ echo -e " Perplexity MCP is already registered."
124
+ read -p " Reconfigure? [y/N] " RECONFIG
125
+ if [[ ! "${RECONFIG:-N}" =~ ^[Yy]$ ]]; then
126
+ ok "Perplexity MCP — keeping existing config"
127
+ return 0
128
+ fi
129
+ echo " Removing existing config..."
130
+ claude mcp remove perplexity 2>/dev/null || true
131
+ fi
132
+
133
+ # Acquire API key
134
+ echo ""
135
+ echo " You need a Perplexity API key (starts with pplx-)."
136
+ echo " Opening API settings page..."
137
+ open_url "https://www.perplexity.ai/settings/api"
138
+ echo ""
139
+ read -s -p " Paste your Perplexity API key: " PPLX_KEY
140
+ echo ""
141
+
142
+ if [[ -z "$PPLX_KEY" ]]; then
143
+ warn "No key entered — skipping Perplexity"
144
+ update_mcp_status "perplexity_status" "skipped"
145
+ return 0
146
+ fi
147
+
148
+ # Validate prefix
149
+ if [[ ! "$PPLX_KEY" =~ ^pplx- ]]; then
150
+ warn "Key doesn't start with 'pplx-' — this may not be a valid Perplexity key"
151
+ read -p " Continue anyway? [y/N] " CONTINUE
152
+ if [[ ! "${CONTINUE:-N}" =~ ^[Yy]$ ]]; then
153
+ unset PPLX_KEY
154
+ update_mcp_status "perplexity_status" "skipped"
155
+ return 0
156
+ fi
157
+ fi
158
+
159
+ # Verify key with API
160
+ echo " Verifying API key..."
161
+ local http_code
162
+ local curl_fmt='%{http_code}'
163
+ http_code=$(curl -s -o /dev/null -w "$curl_fmt" \
164
+ -X POST "https://api.perplexity.ai/chat/completions" \
165
+ -H "Authorization: Bearer $PPLX_KEY" \
166
+ -H "Content-Type: application/json" \
167
+ -d '{"model":"sonar","messages":[{"role":"user","content":"ping"}],"max_tokens":1}' \
168
+ 2>/dev/null || echo "000")
169
+
170
+ if [[ "$http_code" == "200" ]]; then
171
+ ok "API key verified"
172
+ elif [[ "$http_code" == "000" ]]; then
173
+ warn "Could not reach Perplexity API (network issue?) — proceeding anyway"
174
+ else
175
+ warn "API returned HTTP $http_code — key may be invalid"
176
+ read -p " Continue with this key? [y/N] " CONTINUE
177
+ if [[ ! "${CONTINUE:-N}" =~ ^[Yy]$ ]]; then
178
+ unset PPLX_KEY
179
+ update_mcp_status "perplexity_status" "skipped"
180
+ return 0
181
+ fi
182
+ fi
183
+
184
+ # Configure
185
+ echo " Registering Perplexity MCP server..."
186
+ if claude mcp add perplexity -s user -e PERPLEXITY_API_KEY="$PPLX_KEY" -- npx -y @pplx/mcp-server 2>/dev/null; then
187
+ if mcp_server_exists "perplexity"; then
188
+ ok "Perplexity MCP registered"
189
+ update_mcp_status "perplexity_status" "configured"
190
+ else
191
+ warn "Perplexity registered but not showing in mcp list"
192
+ update_mcp_status "perplexity_status" "unverified"
193
+ fi
194
+ else
195
+ fail "Perplexity registration failed"
196
+ echo " Run manually: claude mcp add perplexity -s user -e PERPLEXITY_API_KEY=\"\$KEY\" -- npx -y @pplx/mcp-server"
197
+ update_mcp_status "perplexity_status" "failed"
198
+ fi
199
+
200
+ # Clear key from memory
201
+ unset PPLX_KEY
202
+ }
203
+
204
+ # ═══════════════════════════════════════════
205
+ # Server: GitHub — repo operations
206
+ # ═══════════════════════════════════════════
207
+
208
+ onboard_github() {
209
+ echo -e " ${BOLD}GitHub${NC} — repo operations, PRs, issues, code search"
210
+
211
+ # Check if already registered
212
+ if mcp_server_exists "github"; then
213
+ echo -e " GitHub MCP is already registered."
214
+ read -p " Reconfigure? [y/N] " RECONFIG
215
+ if [[ ! "${RECONFIG:-N}" =~ ^[Yy]$ ]]; then
216
+ ok "GitHub MCP — keeping existing config"
217
+ return 0
218
+ fi
219
+ echo " Removing existing config..."
220
+ claude mcp remove github 2>/dev/null || true
221
+ fi
222
+
223
+ # Acquire PAT
224
+ echo ""
225
+ echo " You need a GitHub Personal Access Token (starts with ghp_ or github_pat_)."
226
+ echo " Opening GitHub token settings..."
227
+ open_url "https://github.com/settings/tokens"
228
+ echo ""
229
+ read -s -p " Paste your GitHub PAT: " GH_TOKEN
230
+ echo ""
231
+
232
+ if [[ -z "$GH_TOKEN" ]]; then
233
+ warn "No token entered — skipping GitHub"
234
+ update_mcp_status "github_mcp_status" "skipped"
235
+ return 0
236
+ fi
237
+
238
+ # Validate prefix
239
+ if [[ ! "$GH_TOKEN" =~ ^(ghp_|github_pat_) ]]; then
240
+ warn "Token doesn't start with 'ghp_' or 'github_pat_' — this may not be a valid PAT"
241
+ read -p " Continue anyway? [y/N] " CONTINUE
242
+ if [[ ! "${CONTINUE:-N}" =~ ^[Yy]$ ]]; then
243
+ unset GH_TOKEN
244
+ update_mcp_status "github_mcp_status" "skipped"
245
+ return 0
246
+ fi
247
+ fi
248
+
249
+ # Verify token
250
+ echo " Verifying GitHub token..."
251
+ local gh_user
252
+ gh_user=$(curl -s -H "Authorization: Bearer $GH_TOKEN" \
253
+ "https://api.github.com/user" 2>/dev/null | jq -r '.login // empty' 2>/dev/null || echo "")
254
+
255
+ if [[ -n "$gh_user" ]]; then
256
+ ok "Authenticated as: $gh_user"
257
+ else
258
+ warn "Could not verify token — proceeding anyway"
259
+ fi
260
+
261
+ # Configure
262
+ echo " Registering GitHub MCP server..."
263
+ if claude mcp add github -s user -e GITHUB_PERSONAL_ACCESS_TOKEN="$GH_TOKEN" -- npx -y @modelcontextprotocol/server-github 2>/dev/null; then
264
+ if mcp_server_exists "github"; then
265
+ ok "GitHub MCP registered"
266
+ update_mcp_status "github_mcp_status" "configured"
267
+ else
268
+ warn "GitHub registered but not showing in mcp list"
269
+ update_mcp_status "github_mcp_status" "unverified"
270
+ fi
271
+ else
272
+ fail "GitHub registration failed"
273
+ echo " Run manually: claude mcp add github -s user -e GITHUB_PERSONAL_ACCESS_TOKEN=\"\$TOKEN\" -- npx -y @modelcontextprotocol/server-github"
274
+ update_mcp_status "github_mcp_status" "failed"
275
+ fi
276
+
277
+ # Clear token from memory
278
+ unset GH_TOKEN
279
+ }
280
+
281
+ # ═══════════════════════════════════════════
282
+ # Entrypoint (standalone mode only)
283
+ # ═══════════════════════════════════════════
284
+
285
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
286
+ TARGET="${1:-all}"
287
+
288
+ echo ""
289
+ echo -e "${BOLD}Praxis — MCP Server Onboarding${NC}"
290
+ echo ""
291
+
292
+ if ! command -v claude &>/dev/null; then
293
+ fail "Claude Code CLI not found. Install it first: npm install -g @anthropic-ai/claude-code"
294
+ exit 1
295
+ fi
296
+
297
+ case "$TARGET" in
298
+ context7)
299
+ onboard_context7
300
+ ;;
301
+ perplexity)
302
+ onboard_perplexity
303
+ ;;
304
+ github)
305
+ onboard_github
306
+ ;;
307
+ all)
308
+ onboard_context7
309
+ echo ""
310
+ read -p " Set up Perplexity? [y/N] " DO_PPLX
311
+ [[ "${DO_PPLX:-N}" =~ ^[Yy]$ ]] && onboard_perplexity
312
+ echo ""
313
+ read -p " Set up GitHub MCP? [y/N] " DO_GH
314
+ [[ "${DO_GH:-N}" =~ ^[Yy]$ ]] && onboard_github
315
+ ;;
316
+ *)
317
+ echo "Usage: bash scripts/onboard-mcp.sh [context7|perplexity|github|all]"
318
+ exit 1
319
+ ;;
320
+ esac
321
+
322
+ echo ""
323
+ echo -e "${BOLD}Current MCP servers:${NC}"
324
+ claude mcp list 2>/dev/null || echo " (none)"
325
+ echo ""
326
+ fi