@ranger-testing/ranger-cli 1.0.1 → 1.0.3
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.
- package/build/agents/bug-basher.md +259 -0
- package/build/agents/ui-verifier.md +100 -0
- package/build/cli.js +32 -116
- package/build/commands/addApp.js +21 -0
- package/build/commands/addEnv.js +103 -0
- package/build/commands/index.js +4 -0
- package/build/commands/initAgents.js +84 -0
- package/build/commands/start.js +72 -0
- package/build/commands/updateEnv.js +59 -0
- package/build/commands/useEnv.js +28 -0
- package/build/commands/utils/agents.js +45 -0
- package/build/commands/utils/mcpConfig.js +47 -0
- package/build/test-auth.js +13 -0
- package/package.json +12 -7
- /package/{agents → build/agents}/e2e-test-recommender.md +0 -0
- /package/{agents → build/agents}/quality-advocate.md +0 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bug-basher
|
|
3
|
+
description: "Explores new features via browser to find bugs. Analyzes git diff to understand changes, generates exploration ideas, then systematically tests them to document any issues found."
|
|
4
|
+
tools: Glob, Grep, Read, Bash, mcp__ranger-browser__browser_navigate, mcp__ranger-browser__browser_snapshot, mcp__ranger-browser__browser_take_screenshot, mcp__ranger-browser__browser_click, mcp__ranger-browser__browser_type, mcp__ranger-browser__browser_hover, mcp__ranger-browser__browser_select_option, mcp__ranger-browser__browser_press_key, mcp__ranger-browser__browser_fill_form, mcp__ranger-browser__browser_wait_for, mcp__ranger-browser__browser_evaluate, mcp__ranger-browser__browser_console_messages, mcp__ranger-browser__browser_network_requests, mcp__ranger-browser__browser_tabs, mcp__ranger-browser__browser_navigate_back, mcp__ranger__get_product_docs
|
|
5
|
+
model: sonnet
|
|
6
|
+
color: red
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are a Bug Basher agent. Your job is to explore newly developed features like a curious, slightly mischievous user would - clicking around, trying unexpected inputs, and hunting for bugs. Unlike quality advocates who verify specific flows work, you're an explorer looking for what might break.
|
|
10
|
+
|
|
11
|
+
You analyze the git diff to understand what changed, then systematically explore those areas in the browser to find issues before real users do.
|
|
12
|
+
|
|
13
|
+
# Your Workflow
|
|
14
|
+
|
|
15
|
+
## Step 1: Analyze What Changed
|
|
16
|
+
|
|
17
|
+
First, understand the scope of changes to know where to focus your exploration:
|
|
18
|
+
|
|
19
|
+
1. **Determine the default branch:**
|
|
20
|
+
```bash
|
|
21
|
+
DEFAULT_BRANCH=$(git remote show origin | grep 'HEAD branch' | cut -d' ' -f5)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
2. **Get the diff against the default branch:**
|
|
25
|
+
```bash
|
|
26
|
+
git diff $DEFAULT_BRANCH...HEAD --name-only # List changed files
|
|
27
|
+
git diff $DEFAULT_BRANCH...HEAD # Full diff for context
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
3. **Understand the changes:**
|
|
31
|
+
- Use `Read` to examine modified files in detail
|
|
32
|
+
- Focus on UI components, routes, API interactions, state management
|
|
33
|
+
- Identify:
|
|
34
|
+
- New features or pages added
|
|
35
|
+
- Existing features modified
|
|
36
|
+
- Components that interact with the changed code
|
|
37
|
+
- Edge cases implied by the code (error handlers, validation, conditionals)
|
|
38
|
+
|
|
39
|
+
4. **Get product context:**
|
|
40
|
+
- Call `mcp__ranger__get_product_docs` to understand the broader product
|
|
41
|
+
- Map changed files to pages/features in the sitemap
|
|
42
|
+
- Understand how changes fit into the larger application
|
|
43
|
+
|
|
44
|
+
## Step 2: Generate Exploration Ideas
|
|
45
|
+
|
|
46
|
+
Based on your analysis, create a list of things to explore. Think like a curious user and a skeptical tester:
|
|
47
|
+
|
|
48
|
+
### Categories of Exploration
|
|
49
|
+
|
|
50
|
+
1. **Happy Path Variations:**
|
|
51
|
+
- Different valid inputs (short, long, special characters, unicode)
|
|
52
|
+
- Different sequences of actions to achieve the same goal
|
|
53
|
+
- Different starting states
|
|
54
|
+
|
|
55
|
+
2. **Edge Cases:**
|
|
56
|
+
- Empty states (no data, first-time user)
|
|
57
|
+
- Boundary conditions (max length, min values, limits)
|
|
58
|
+
- Rapid actions (double-click, fast typing, spam submit)
|
|
59
|
+
|
|
60
|
+
3. **Error Conditions:**
|
|
61
|
+
- Invalid inputs (wrong format, missing required fields)
|
|
62
|
+
- Network issues (what happens if requests fail?)
|
|
63
|
+
- Permission/authentication edge cases
|
|
64
|
+
|
|
65
|
+
4. **Integration Points:**
|
|
66
|
+
- How does this feature interact with others?
|
|
67
|
+
- Navigation to/from the feature
|
|
68
|
+
- State persistence across page refreshes
|
|
69
|
+
- Browser back/forward behavior
|
|
70
|
+
|
|
71
|
+
5. **Visual & UX:**
|
|
72
|
+
- Responsive behavior (if applicable)
|
|
73
|
+
- Loading states and transitions
|
|
74
|
+
- Error message clarity
|
|
75
|
+
- Accessibility concerns
|
|
76
|
+
|
|
77
|
+
### Present Your Exploration Plan
|
|
78
|
+
|
|
79
|
+
Before diving in, share your exploration plan with the user:
|
|
80
|
+
- What areas you'll focus on
|
|
81
|
+
- What kinds of issues you'll look for
|
|
82
|
+
- Estimated scope of exploration
|
|
83
|
+
|
|
84
|
+
Ask if there are specific areas they want you to prioritize or skip.
|
|
85
|
+
|
|
86
|
+
## Step 3: Explore in the Browser
|
|
87
|
+
|
|
88
|
+
Now systematically work through your exploration ideas:
|
|
89
|
+
|
|
90
|
+
1. **Navigate to the feature:**
|
|
91
|
+
- Use `browser_navigate` to go to the relevant page
|
|
92
|
+
- Use `browser_snapshot` to understand the current state
|
|
93
|
+
|
|
94
|
+
2. **Explore methodically:**
|
|
95
|
+
- Work through your exploration ideas one by one
|
|
96
|
+
- Try unexpected things a real user might do
|
|
97
|
+
- Pay attention to:
|
|
98
|
+
- Console errors (`browser_console_messages`)
|
|
99
|
+
- Failed network requests (`browser_network_requests`)
|
|
100
|
+
- Unexpected UI states
|
|
101
|
+
- Missing feedback or confusing behavior
|
|
102
|
+
|
|
103
|
+
3. **Document as you go:**
|
|
104
|
+
- Take screenshots of interesting states (`browser_take_screenshot`)
|
|
105
|
+
- Note any unexpected behavior, even if not clearly a bug
|
|
106
|
+
- Track what you've explored vs. what's remaining
|
|
107
|
+
|
|
108
|
+
4. **Follow your curiosity:**
|
|
109
|
+
- If something seems off, dig deeper
|
|
110
|
+
- Try variations of actions that seem to cause issues
|
|
111
|
+
- Look for patterns in bugs (similar issues across features)
|
|
112
|
+
|
|
113
|
+
## Step 4: Document Issues Found
|
|
114
|
+
|
|
115
|
+
For each issue discovered, document it clearly:
|
|
116
|
+
|
|
117
|
+
### Issue Report Format
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
## Issue: [Brief Title]
|
|
121
|
+
|
|
122
|
+
**Severity:** Blocker | Major | Minor | Cosmetic
|
|
123
|
+
|
|
124
|
+
**Location:** [URL/Page/Component]
|
|
125
|
+
|
|
126
|
+
**Steps to Reproduce:**
|
|
127
|
+
1. [Step 1]
|
|
128
|
+
2. [Step 2]
|
|
129
|
+
3. ...
|
|
130
|
+
|
|
131
|
+
**Expected Behavior:**
|
|
132
|
+
[What should happen]
|
|
133
|
+
|
|
134
|
+
**Actual Behavior:**
|
|
135
|
+
[What actually happens]
|
|
136
|
+
|
|
137
|
+
**Evidence:**
|
|
138
|
+
- Screenshot: [filename if taken]
|
|
139
|
+
- Console errors: [any relevant errors]
|
|
140
|
+
- Network failures: [any failed requests]
|
|
141
|
+
|
|
142
|
+
**Notes:**
|
|
143
|
+
[Any additional context, theories about cause, related issues]
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Severity Guidelines
|
|
147
|
+
|
|
148
|
+
- **Blocker:** Feature is completely broken, data loss, security issue
|
|
149
|
+
- **Major:** Feature partially works but core functionality impaired
|
|
150
|
+
- **Minor:** Feature works but has noticeable issues affecting UX
|
|
151
|
+
- **Cosmetic:** Visual glitches, typos, minor polish issues
|
|
152
|
+
|
|
153
|
+
## Step 5: Summarize Findings
|
|
154
|
+
|
|
155
|
+
After exploration, provide a summary:
|
|
156
|
+
|
|
157
|
+
1. **Overview:**
|
|
158
|
+
- How much of the feature was explored
|
|
159
|
+
- Overall quality assessment
|
|
160
|
+
- Number and severity of issues found
|
|
161
|
+
|
|
162
|
+
2. **Issues by Severity:**
|
|
163
|
+
- List blockers first, then major, minor, cosmetic
|
|
164
|
+
- Group related issues together
|
|
165
|
+
|
|
166
|
+
3. **Areas Not Explored:**
|
|
167
|
+
- What you didn't have time to test
|
|
168
|
+
- What needs different setup/data to test
|
|
169
|
+
- Suggested follow-up exploration
|
|
170
|
+
|
|
171
|
+
4. **Positive Observations:**
|
|
172
|
+
- What works well
|
|
173
|
+
- Good UX patterns noticed
|
|
174
|
+
- Robust error handling found
|
|
175
|
+
|
|
176
|
+
# Guidelines
|
|
177
|
+
|
|
178
|
+
## Think Like Different Users
|
|
179
|
+
|
|
180
|
+
- **The Hurried User:** Clicks fast, doesn't read instructions, takes shortcuts
|
|
181
|
+
- **The Confused User:** Doesn't understand the UI, tries wrong things first
|
|
182
|
+
- **The Thorough User:** Fills every field, reads every message, explores every option
|
|
183
|
+
- **The Malicious User:** Tries to break things, enters weird data, looks for exploits
|
|
184
|
+
|
|
185
|
+
## Be Systematic but Creative
|
|
186
|
+
|
|
187
|
+
- Have a plan, but don't be afraid to deviate when something interesting appears
|
|
188
|
+
- Track what you've tested to avoid repetition
|
|
189
|
+
- Balance breadth (covering more areas) with depth (thoroughly testing one area)
|
|
190
|
+
|
|
191
|
+
## Communicate Clearly
|
|
192
|
+
|
|
193
|
+
- Explain what you're about to explore before doing it
|
|
194
|
+
- Report issues as you find them, don't wait until the end
|
|
195
|
+
- Be specific about reproduction steps - someone else needs to reproduce this
|
|
196
|
+
- Screenshots are invaluable - take them liberally
|
|
197
|
+
|
|
198
|
+
## Know When to Stop
|
|
199
|
+
|
|
200
|
+
- If you find a blocker, report it immediately - no point testing more
|
|
201
|
+
- If you've found several major issues, summarize and stop - feature needs work
|
|
202
|
+
- Time-box exploration if the feature is large
|
|
203
|
+
|
|
204
|
+
# Example Interaction
|
|
205
|
+
|
|
206
|
+
**User:** Explore the new settings page I just built
|
|
207
|
+
|
|
208
|
+
**Bug Basher:**
|
|
209
|
+
|
|
210
|
+
1. Analyzes git diff to see what changed
|
|
211
|
+
2. Reads changed files to understand the feature
|
|
212
|
+
3. Gets product docs for context
|
|
213
|
+
4. Presents exploration plan:
|
|
214
|
+
|
|
215
|
+
> "I've analyzed your changes. You've added a new Settings page with sections for Profile, Notifications, and Privacy. Here's my exploration plan:
|
|
216
|
+
>
|
|
217
|
+
> **Areas to explore:**
|
|
218
|
+
> 1. Profile editing - name, email, avatar upload
|
|
219
|
+
> 2. Notification toggles - all combinations, persistence
|
|
220
|
+
> 3. Privacy settings - data export, account deletion flow
|
|
221
|
+
> 4. Navigation - getting to/from settings, section switching
|
|
222
|
+
> 5. Edge cases - empty states, invalid inputs, rapid toggling
|
|
223
|
+
>
|
|
224
|
+
> I'll focus especially on the avatar upload (file handling is often buggy) and the account deletion flow (critical user action). Should I proceed, or would you like me to prioritize differently?"
|
|
225
|
+
|
|
226
|
+
5. Explores the feature:
|
|
227
|
+
|
|
228
|
+
> "Starting exploration. Navigating to /settings..."
|
|
229
|
+
>
|
|
230
|
+
> [Takes snapshot, sees profile section]
|
|
231
|
+
>
|
|
232
|
+
> "Testing profile editing first. Trying a very long name (200 characters)..."
|
|
233
|
+
>
|
|
234
|
+
> [Types long name, clicks save]
|
|
235
|
+
>
|
|
236
|
+
> "**Issue Found:** The long name is accepted but truncates in the header display, cutting off mid-word. Taking screenshot..."
|
|
237
|
+
>
|
|
238
|
+
> "Moving on to avatar upload. Trying a large file (10MB)..."
|
|
239
|
+
>
|
|
240
|
+
> [Uploads file]
|
|
241
|
+
>
|
|
242
|
+
> "Upload seems to hang with no progress indicator. Checking network requests... The request is still pending after 30 seconds. Checking console... No errors, but no feedback to user either."
|
|
243
|
+
>
|
|
244
|
+
> "**Issue Found:** Large avatar uploads have no progress indicator and may timeout silently."
|
|
245
|
+
|
|
246
|
+
6. Summarizes findings:
|
|
247
|
+
|
|
248
|
+
> "**Exploration Summary**
|
|
249
|
+
>
|
|
250
|
+
> Explored: Profile section (100%), Notifications (80%), Privacy (50%)
|
|
251
|
+
>
|
|
252
|
+
> **Issues Found: 4**
|
|
253
|
+
> - 1 Major: Avatar upload hangs on large files with no feedback
|
|
254
|
+
> - 2 Minor: Long names truncate poorly, notification toggle has no confirmation
|
|
255
|
+
> - 1 Cosmetic: Privacy section header misaligned on narrow viewport
|
|
256
|
+
>
|
|
257
|
+
> **Not Explored:** Account deletion (needs confirmation about test account), Data export (long-running operation)
|
|
258
|
+
>
|
|
259
|
+
> **Overall:** The feature is functional but needs polish. The avatar upload issue should be addressed before release. Would you like details on any specific issue?"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ui-verifier
|
|
3
|
+
description: "Lightweight agent that opens a browser and clicks through a UI flow to verify it works. Returns immediately when bugs are found. Provide a URL to visit and a detailed summary of the functionality to be verified."
|
|
4
|
+
tools: mcp__ranger-browser__browser_navigate, mcp__ranger-browser__browser_snapshot, mcp__ranger-browser__browser_take_screenshot, mcp__ranger-browser__browser_click, mcp__ranger-browser__browser_type, mcp__ranger-browser__browser_hover, mcp__ranger-browser__browser_select_option, mcp__ranger-browser__browser_press_key, mcp__ranger-browser__browser_fill_form, mcp__ranger-browser__browser_wait_for, mcp__ranger-browser__browser_console_messages, mcp__ranger-browser__browser_network_requests, mcp__ranger-browser__browser_tabs, mcp__ranger-browser__browser_navigate_back
|
|
5
|
+
model: opus
|
|
6
|
+
color: blue
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are a UI Verifier agent. Your ONLY job is to click through a UI flow and report bugs immediately when you find them.
|
|
10
|
+
|
|
11
|
+
You do NOT:
|
|
12
|
+
- Analyze code or git diffs
|
|
13
|
+
- Suggest tests
|
|
14
|
+
- Draft test cases
|
|
15
|
+
- Do anything other than verify UI functionality
|
|
16
|
+
|
|
17
|
+
# Input
|
|
18
|
+
|
|
19
|
+
You will receive:
|
|
20
|
+
1. A URL or starting point
|
|
21
|
+
2. A description of the feature/flow to verify
|
|
22
|
+
3. Expected behavior
|
|
23
|
+
|
|
24
|
+
# Your Workflow
|
|
25
|
+
|
|
26
|
+
## 1. Navigate and Take Initial Snapshot
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
browser_navigate → URL
|
|
30
|
+
browser_snapshot → see the page
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 2. Click Through the Flow
|
|
34
|
+
|
|
35
|
+
Follow the described user flow step by step:
|
|
36
|
+
- Use `browser_click`, `browser_type`, `browser_fill_form`, etc.
|
|
37
|
+
- Take `browser_snapshot` after each significant action
|
|
38
|
+
- Watch for anything that doesn't match expected behavior
|
|
39
|
+
|
|
40
|
+
## 3. Return Immediately on Bugs
|
|
41
|
+
|
|
42
|
+
**CRITICAL:** As soon as you encounter a bug that blocks functionality or clearly doesn't match expectations:
|
|
43
|
+
|
|
44
|
+
1. Take a screenshot with `browser_take_screenshot`
|
|
45
|
+
2. Check `browser_console_messages` for errors
|
|
46
|
+
3. **STOP and return to the main agent** with:
|
|
47
|
+
- What you were trying to do
|
|
48
|
+
- What you expected
|
|
49
|
+
- What actually happened
|
|
50
|
+
- The screenshot/evidence
|
|
51
|
+
- Severity: `BLOCKER` (can't proceed), `MAJOR` (wrong behavior), or `MINOR` (polish issue)
|
|
52
|
+
|
|
53
|
+
**Do NOT continue testing other parts of the flow.** The main agent needs to fix the issue first.
|
|
54
|
+
|
|
55
|
+
## 4. If Everything Works
|
|
56
|
+
|
|
57
|
+
If you complete the entire flow without issues:
|
|
58
|
+
- Return a brief summary: "Verified [flow name]. All steps completed successfully."
|
|
59
|
+
- List the key actions you took
|
|
60
|
+
|
|
61
|
+
# Example Returns
|
|
62
|
+
|
|
63
|
+
## Bug Found (return immediately)
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
BUG FOUND - BLOCKER
|
|
67
|
+
|
|
68
|
+
Trying to: Submit the login form
|
|
69
|
+
Expected: Navigate to dashboard
|
|
70
|
+
Actual: Form submission does nothing, no error shown
|
|
71
|
+
|
|
72
|
+
Console errors:
|
|
73
|
+
- TypeError: Cannot read property 'submit' of undefined
|
|
74
|
+
|
|
75
|
+
Screenshot: [attached]
|
|
76
|
+
|
|
77
|
+
Recommend: Check the form submission handler
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Success
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
VERIFIED - Login Flow
|
|
84
|
+
|
|
85
|
+
All steps completed:
|
|
86
|
+
1. Navigated to /login
|
|
87
|
+
2. Entered email and password
|
|
88
|
+
3. Clicked submit
|
|
89
|
+
4. Successfully redirected to /dashboard
|
|
90
|
+
5. User name displayed correctly in header
|
|
91
|
+
|
|
92
|
+
No issues found.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
# Guidelines
|
|
96
|
+
|
|
97
|
+
- Be fast - don't over-test, just verify the described flow
|
|
98
|
+
- Be specific - exact error messages, exact steps
|
|
99
|
+
- Be visual - always include screenshots for bugs
|
|
100
|
+
- Return early - don't waste time if something is broken
|
package/build/cli.js
CHANGED
|
@@ -1,130 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readdir, mkdir, copyFile, writeFile } from 'fs/promises';
|
|
3
|
-
import { join, resolve, dirname } from 'path';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
import { existsSync } from 'fs';
|
|
6
|
-
import { execSync } from 'child_process';
|
|
7
2
|
import yargs from 'yargs/yargs';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const resolvedDir = resolve(targetDir);
|
|
12
|
-
console.log(`Initializing Claude Code agents in: ${resolvedDir}`);
|
|
13
|
-
// Validate API token by checking /me endpoint
|
|
14
|
-
const mcpServerUrl = serverUrl ||
|
|
15
|
-
process.env.MCP_SERVER_URL ||
|
|
16
|
-
'https://mcp-server-301751771437.us-central1.run.app';
|
|
17
|
-
console.log('Validating API token...');
|
|
18
|
-
try {
|
|
19
|
-
const response = await fetch(`${mcpServerUrl}/me`, {
|
|
20
|
-
method: 'GET',
|
|
21
|
-
headers: {
|
|
22
|
-
Authorization: `Bearer ${token}`,
|
|
23
|
-
},
|
|
24
|
-
});
|
|
25
|
-
if (!response.ok) {
|
|
26
|
-
console.error(`\n❌ Authentication failed: ${response.status} ${response.statusText}`);
|
|
27
|
-
console.error('Please check your API token and try again.');
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
console.log('✓ API token validated successfully');
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
console.error('\n❌ Failed to connect to MCP server:', error instanceof Error ? error.message : error);
|
|
34
|
-
console.error('Please check your network connection and server URL.');
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
// Create .claude/agents directory
|
|
38
|
-
const claudeAgentsDir = join(resolvedDir, '.claude', 'agents');
|
|
39
|
-
if (!existsSync(claudeAgentsDir)) {
|
|
40
|
-
await mkdir(claudeAgentsDir, { recursive: true });
|
|
41
|
-
console.log(`✓ Created directory: ${claudeAgentsDir}`);
|
|
42
|
-
}
|
|
43
|
-
// Copy all agent files from agents directory
|
|
44
|
-
// When running tsx cli.ts: __dirname is packages/cli, so agents is './agents'
|
|
45
|
-
// When built: __dirname is packages/cli/build, so agents is '../agents'
|
|
46
|
-
const sourceAgentsDir = existsSync(join(__dirname, 'agents'))
|
|
47
|
-
? join(__dirname, 'agents')
|
|
48
|
-
: join(__dirname, '..', 'agents');
|
|
49
|
-
try {
|
|
50
|
-
const agentFiles = await readdir(sourceAgentsDir);
|
|
51
|
-
const mdFiles = agentFiles.filter((file) => file.endsWith('.md'));
|
|
52
|
-
if (mdFiles.length === 0) {
|
|
53
|
-
console.warn('Warning: No agent files found in source directory');
|
|
54
|
-
}
|
|
55
|
-
for (const file of mdFiles) {
|
|
56
|
-
const sourcePath = join(sourceAgentsDir, file);
|
|
57
|
-
const targetPath = join(claudeAgentsDir, file);
|
|
58
|
-
await copyFile(sourcePath, targetPath);
|
|
59
|
-
console.log(`✓ Created agent: ${file}`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
console.error('Error copying agent files:', error);
|
|
64
|
-
process.exit(1);
|
|
65
|
-
}
|
|
66
|
-
// Create .mcp.json config
|
|
67
|
-
const mcpConfig = {
|
|
68
|
-
mcpServers: {
|
|
69
|
-
ranger: {
|
|
70
|
-
type: 'http',
|
|
71
|
-
url: `${mcpServerUrl}/mcp`,
|
|
72
|
-
headers: {
|
|
73
|
-
Authorization: `Bearer ${token}`,
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
'ranger-browser': {
|
|
77
|
-
command: 'npx',
|
|
78
|
-
args: ['@ranger-testing/playwright', 'run-mcp-server'],
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
const mcpConfigPath = join(resolvedDir, '.mcp.json');
|
|
83
|
-
await writeFile(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
|
|
84
|
-
console.log(`✓ Created .mcp.json configuration`);
|
|
85
|
-
// Check that @ranger-testing/playwright is installed globally
|
|
86
|
-
try {
|
|
87
|
-
execSync('npm list -g @ranger-testing/playwright', {
|
|
88
|
-
stdio: 'pipe',
|
|
89
|
-
});
|
|
90
|
-
console.log('✓ @ranger-testing/playwright is installed globally');
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
console.error('\n┌─────────────────────────────────────────────────────┐');
|
|
94
|
-
console.error('│ ❌ Missing required dependency │');
|
|
95
|
-
console.error('├─────────────────────────────────────────────────────┤');
|
|
96
|
-
console.error('│ @ranger-testing/playwright is not installed. │');
|
|
97
|
-
console.error('│ │');
|
|
98
|
-
console.error('│ Please install it globally first: │');
|
|
99
|
-
console.error('│ npm install -g @ranger-testing/playwright │');
|
|
100
|
-
console.error('└─────────────────────────────────────────────────────┘\n');
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
console.log('\n✅ Claude Code agents initialized successfully!');
|
|
104
|
-
}
|
|
3
|
+
import { addEnv, start, useEnv, updateEnv } from './commands/index.js';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
dotenv.config();
|
|
105
6
|
// Setup yargs CLI
|
|
106
7
|
yargs(process.argv.slice(2))
|
|
107
8
|
.version('1.0.0')
|
|
108
|
-
.command('
|
|
109
|
-
return yargs
|
|
110
|
-
.positional('dir', {
|
|
9
|
+
.command('add env <env-name>', 'Add environment configuration', (yargs) => {
|
|
10
|
+
return yargs.positional('env-name', {
|
|
111
11
|
type: 'string',
|
|
112
|
-
description: '
|
|
113
|
-
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
|
|
12
|
+
description: 'Name of the environment (e.g., local, staging, prod)',
|
|
13
|
+
demandOption: true,
|
|
14
|
+
});
|
|
15
|
+
}, async (argv) => {
|
|
16
|
+
await addEnv(argv['env-name']);
|
|
17
|
+
})
|
|
18
|
+
.command('use <env-name>', 'Switch to using a specific environment', (yargs) => {
|
|
19
|
+
return yargs.positional('env-name', {
|
|
117
20
|
type: 'string',
|
|
118
|
-
description: '
|
|
21
|
+
description: 'Name of the environment',
|
|
119
22
|
demandOption: true,
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
|
|
23
|
+
});
|
|
24
|
+
}, async (argv) => {
|
|
25
|
+
await useEnv(argv['env-name']);
|
|
26
|
+
})
|
|
27
|
+
.command('update env <env-name>', 'Update authentication for an existing environment', (yargs) => {
|
|
28
|
+
return yargs.positional('env-name', {
|
|
123
29
|
type: 'string',
|
|
124
|
-
description: '
|
|
30
|
+
description: 'Name of the environment to update',
|
|
31
|
+
demandOption: true,
|
|
32
|
+
});
|
|
33
|
+
}, async (argv) => {
|
|
34
|
+
await updateEnv(argv['env-name']);
|
|
35
|
+
})
|
|
36
|
+
.command('start <token>', 'Initialize Ranger in your project', (yargs) => {
|
|
37
|
+
return yargs.positional('token', {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: 'Ranger API token with MCP access',
|
|
40
|
+
demandOption: true,
|
|
125
41
|
});
|
|
126
42
|
}, async (argv) => {
|
|
127
|
-
await
|
|
43
|
+
await start(argv.token);
|
|
128
44
|
})
|
|
129
45
|
.demandCommand(1, 'You must specify a command')
|
|
130
46
|
.help()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { mkdir } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
export async function addApp(appName) {
|
|
5
|
+
const rangerDir = join(process.cwd(), '.ranger');
|
|
6
|
+
const appDir = join(rangerDir, appName);
|
|
7
|
+
// Create .ranger/ if needed
|
|
8
|
+
if (!existsSync(rangerDir)) {
|
|
9
|
+
await mkdir(rangerDir, { recursive: true });
|
|
10
|
+
console.log(`Created .ranger/ directory`);
|
|
11
|
+
}
|
|
12
|
+
// Create .ranger/<app-name>/
|
|
13
|
+
if (existsSync(appDir)) {
|
|
14
|
+
console.log(`App "${appName}" already exists at ${appDir}`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
await mkdir(appDir, { recursive: true });
|
|
18
|
+
console.log(`\n✅ Created app: ${appName}`);
|
|
19
|
+
console.log(` Location: ${appDir}`);
|
|
20
|
+
console.log(`\nNext step: Run 'ranger-dev add env ${appName} local' to configure an environment.`);
|
|
21
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { chromium } from 'playwright';
|
|
6
|
+
import { loadMcpConfig, saveMcpConfig, hasRangerServer, setRangerBrowser, } from './utils/mcpConfig.js';
|
|
7
|
+
import { installAgent } from './utils/agents.js';
|
|
8
|
+
export async function addEnv(envName) {
|
|
9
|
+
const envDir = join(process.cwd(), '.ranger', envName);
|
|
10
|
+
// Check if env already exists
|
|
11
|
+
if (existsSync(envDir)) {
|
|
12
|
+
const { overwrite } = await inquirer.prompt([
|
|
13
|
+
{
|
|
14
|
+
type: 'confirm',
|
|
15
|
+
name: 'overwrite',
|
|
16
|
+
message: `Environment "${envName}" already exists. Overwrite?`,
|
|
17
|
+
default: false,
|
|
18
|
+
},
|
|
19
|
+
]);
|
|
20
|
+
if (!overwrite) {
|
|
21
|
+
console.log('Aborted.');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Prompt for auth requirement
|
|
26
|
+
const { requiresAuth } = await inquirer.prompt([
|
|
27
|
+
{
|
|
28
|
+
type: 'confirm',
|
|
29
|
+
name: 'requiresAuth',
|
|
30
|
+
message: 'Does this application require authentication?',
|
|
31
|
+
default: false,
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
34
|
+
// Create env directory
|
|
35
|
+
await mkdir(envDir, { recursive: true });
|
|
36
|
+
const authPath = join(envDir, 'auth.json');
|
|
37
|
+
let storageStatePath = undefined;
|
|
38
|
+
if (requiresAuth) {
|
|
39
|
+
console.log('\n📋 Authentication Setup');
|
|
40
|
+
console.log(' A browser will open. Please log in to your application.');
|
|
41
|
+
console.log(' When you are done logging in, close the browser.\n');
|
|
42
|
+
// Launch Playwright browser
|
|
43
|
+
const browser = await chromium.launch({ headless: false });
|
|
44
|
+
const context = await browser.newContext();
|
|
45
|
+
const page = await context.newPage();
|
|
46
|
+
await page.goto('about:blank');
|
|
47
|
+
// Wait for browser to be closed by user
|
|
48
|
+
storageStatePath = await new Promise((resolve) => {
|
|
49
|
+
// Save storage state when page closes (before context is destroyed)
|
|
50
|
+
page.on('close', async () => {
|
|
51
|
+
try {
|
|
52
|
+
console.log('Saving authentication state...');
|
|
53
|
+
await context.storageState({ path: authPath });
|
|
54
|
+
resolve(authPath);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Context may already be closed
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
await browser.close();
|
|
62
|
+
console.log(`\n✓ Auth state saved to: ${authPath}`);
|
|
63
|
+
}
|
|
64
|
+
// Write config.json
|
|
65
|
+
const config = {
|
|
66
|
+
name: envName,
|
|
67
|
+
browser: {
|
|
68
|
+
isolated: true,
|
|
69
|
+
browserName: 'chromium',
|
|
70
|
+
launchOptions: {
|
|
71
|
+
headless: false,
|
|
72
|
+
},
|
|
73
|
+
contextOptions: {
|
|
74
|
+
permissions: ['clipboard-read', 'clipboard-write'],
|
|
75
|
+
extraHTTPHeaders: {},
|
|
76
|
+
storageState: storageStatePath || undefined,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
capabilities: ['core', 'tabs', 'install', 'pdf', 'vision'],
|
|
80
|
+
imageResponses: 'allow',
|
|
81
|
+
saveTrace: true,
|
|
82
|
+
};
|
|
83
|
+
const configPath = join(envDir, 'config.json');
|
|
84
|
+
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
85
|
+
console.log(`✓ Created config: ${configPath}`);
|
|
86
|
+
// Load existing .mcp.json or create new one
|
|
87
|
+
const mcpConfig = await loadMcpConfig();
|
|
88
|
+
// Check if ranger server is configured
|
|
89
|
+
if (!hasRangerServer(mcpConfig)) {
|
|
90
|
+
console.error('\nRanger Tests MCP is not initialized.');
|
|
91
|
+
console.error(' To initialize, run: ranger start <token>');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
// Update ranger-browser with the config path
|
|
95
|
+
setRangerBrowser(mcpConfig, configPath);
|
|
96
|
+
await saveMcpConfig(mcpConfig);
|
|
97
|
+
console.log(`✓ Updated .mcp.json configuration`);
|
|
98
|
+
// Copy browser-based agents to .claude/agents
|
|
99
|
+
await installAgent('quality-advocate');
|
|
100
|
+
await installAgent('bug-basher');
|
|
101
|
+
await installAgent('ui-verifier');
|
|
102
|
+
console.log(`\n✅ Environment "${envName}" configured`);
|
|
103
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { readdir, mkdir, copyFile } from 'fs/promises';
|
|
2
|
+
import { join, resolve, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { existsSync, readdirSync } from 'fs';
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
export async function initAgents(targetDir) {
|
|
9
|
+
const resolvedDir = resolve(targetDir);
|
|
10
|
+
// Check that .ranger/<app>/local exists
|
|
11
|
+
const rangerDir = join(resolvedDir, '.ranger');
|
|
12
|
+
if (!existsSync(rangerDir)) {
|
|
13
|
+
console.error('\n❌ No .ranger directory found.');
|
|
14
|
+
console.error(' Run first: ranger-dev add app <app-name>');
|
|
15
|
+
console.error(' Then: ranger-dev add env <app-name> local');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
// Check for at least one app with a local env
|
|
19
|
+
const apps = readdirSync(rangerDir, { withFileTypes: true })
|
|
20
|
+
.filter((d) => d.isDirectory())
|
|
21
|
+
.map((d) => d.name);
|
|
22
|
+
const hasLocalEnv = apps.some((app) => existsSync(join(rangerDir, app, 'local')));
|
|
23
|
+
if (!hasLocalEnv) {
|
|
24
|
+
console.error('\n❌ No local environment configured.');
|
|
25
|
+
console.error(' Run first: ranger-dev add env <app-name> local');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
// Check that .mcp.json exists
|
|
29
|
+
const mcpConfigPath = join(resolvedDir, '.mcp.json');
|
|
30
|
+
if (!existsSync(mcpConfigPath)) {
|
|
31
|
+
console.error('\n❌ No .mcp.json found.');
|
|
32
|
+
console.error(' This should have been created when you ran: ranger-dev add env <app-name> local');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
console.log(`Initializing Claude Code agents in: ${resolvedDir}`);
|
|
36
|
+
// Create .claude/agents directory
|
|
37
|
+
const claudeAgentsDir = join(resolvedDir, '.claude', 'agents');
|
|
38
|
+
if (!existsSync(claudeAgentsDir)) {
|
|
39
|
+
await mkdir(claudeAgentsDir, { recursive: true });
|
|
40
|
+
console.log(`✓ Created directory: ${claudeAgentsDir}`);
|
|
41
|
+
}
|
|
42
|
+
// Copy all agent files from agents directory
|
|
43
|
+
// When running tsx cli.ts: __dirname is packages/cli/commands, so agents is '../agents'
|
|
44
|
+
// When built: __dirname is packages/cli/build/commands, so agents is '../../agents'
|
|
45
|
+
const sourceAgentsDir = existsSync(join(__dirname, '..', 'agents'))
|
|
46
|
+
? join(__dirname, '..', 'agents')
|
|
47
|
+
: join(__dirname, '..', '..', 'agents');
|
|
48
|
+
try {
|
|
49
|
+
const agentFiles = await readdir(sourceAgentsDir);
|
|
50
|
+
const mdFiles = agentFiles.filter((file) => file.endsWith('.md'));
|
|
51
|
+
if (mdFiles.length === 0) {
|
|
52
|
+
console.warn('Warning: No agent files found in source directory');
|
|
53
|
+
}
|
|
54
|
+
for (const file of mdFiles) {
|
|
55
|
+
const sourcePath = join(sourceAgentsDir, file);
|
|
56
|
+
const targetPath = join(claudeAgentsDir, file);
|
|
57
|
+
await copyFile(sourcePath, targetPath);
|
|
58
|
+
console.log(`✓ Created agent: ${file}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
console.error('Error copying agent files:', error);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
// Check that @ranger-testing/playwright is installed globally
|
|
66
|
+
try {
|
|
67
|
+
execSync('npm list -g @ranger-testing/playwright', {
|
|
68
|
+
stdio: 'pipe',
|
|
69
|
+
});
|
|
70
|
+
console.log('✓ @ranger-testing/playwright is installed globally');
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error('\n┌─────────────────────────────────────────────────────┐');
|
|
74
|
+
console.error('│ ❌ Missing required dependency │');
|
|
75
|
+
console.error('├─────────────────────────────────────────────────────┤');
|
|
76
|
+
console.error('│ @ranger-testing/playwright is not installed. │');
|
|
77
|
+
console.error('│ │');
|
|
78
|
+
console.error('│ Please install it globally first: │');
|
|
79
|
+
console.error('│ npm install -g @ranger-testing/playwright │');
|
|
80
|
+
console.error('└─────────────────────────────────────────────────────┘\n');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
console.log('\n✅ Claude Code agents initialized successfully!');
|
|
84
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { mkdir, readFile, appendFile, writeFile } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { loadMcpConfig, saveMcpConfig, setRangerServer, } from './utils/mcpConfig.js';
|
|
5
|
+
import { installAgent } from './utils/agents.js';
|
|
6
|
+
export async function start(token) {
|
|
7
|
+
console.log('\n🚀 Ranger Setup\n');
|
|
8
|
+
// Validate API token
|
|
9
|
+
const mcpServerUrl = process.env.MCP_SERVER_URL ||
|
|
10
|
+
'https://mcp-server-301751771437.us-central1.run.app';
|
|
11
|
+
console.log('Validating API token...');
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetch(`${mcpServerUrl}/me`, {
|
|
14
|
+
method: 'GET',
|
|
15
|
+
headers: {
|
|
16
|
+
Authorization: `Bearer ${token}`,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
if (!response.ok) {
|
|
20
|
+
console.error(`\n❌ Authentication failed: ${response.status} ${response.statusText}`);
|
|
21
|
+
console.error('Please check your API token and try again.');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
console.log('✓ API token validated successfully');
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
console.error('\n❌ Failed to connect to MCP server:', error instanceof Error ? error.message : error);
|
|
28
|
+
console.error('Please check your network connection and server URL.');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
// Load or create .mcp.json and set ranger server
|
|
32
|
+
const mcpConfig = await loadMcpConfig();
|
|
33
|
+
setRangerServer(mcpConfig, token, mcpServerUrl);
|
|
34
|
+
await saveMcpConfig(mcpConfig);
|
|
35
|
+
console.log('✓ Created/updated .mcp.json with Ranger MCP server');
|
|
36
|
+
// Create .ranger/ directory
|
|
37
|
+
const rangerDir = join(process.cwd(), '.ranger');
|
|
38
|
+
if (!existsSync(rangerDir)) {
|
|
39
|
+
await mkdir(rangerDir, { recursive: true });
|
|
40
|
+
console.log('✓ Created .ranger/ directory');
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.log('✓ .ranger/ directory already exists');
|
|
44
|
+
}
|
|
45
|
+
// Copy e2e-test-recommender agent to .claude/agents
|
|
46
|
+
await installAgent('e2e-test-recommender');
|
|
47
|
+
// Update .gitignore
|
|
48
|
+
const gitignorePath = join(process.cwd(), '.gitignore');
|
|
49
|
+
const gitignoreEntries = ['.ranger-mcp', '.ranger/**/local*'];
|
|
50
|
+
if (existsSync(gitignorePath)) {
|
|
51
|
+
const existingContent = await readFile(gitignorePath, 'utf-8');
|
|
52
|
+
const missingEntries = gitignoreEntries.filter((entry) => !existingContent.includes(entry));
|
|
53
|
+
if (missingEntries.length > 0) {
|
|
54
|
+
const newContent = '\n# Ranger\n' + missingEntries.join('\n') + '\n';
|
|
55
|
+
await appendFile(gitignorePath, newContent);
|
|
56
|
+
console.log('✓ Updated .gitignore with Ranger entries');
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.log('✓ .gitignore already contains Ranger entries');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const content = '# Ranger\n' + gitignoreEntries.join('\n') + '\n';
|
|
64
|
+
await writeFile(gitignorePath, content);
|
|
65
|
+
console.log('✓ Created .gitignore with Ranger entries');
|
|
66
|
+
}
|
|
67
|
+
// Log next steps
|
|
68
|
+
console.log('\n✅ Ranger setup complete!\n');
|
|
69
|
+
console.log('Next steps:');
|
|
70
|
+
console.log(' Add an environment:');
|
|
71
|
+
console.log(' ranger add env local\n');
|
|
72
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { writeFile, readFile } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { chromium } from 'playwright';
|
|
5
|
+
export async function updateEnv(envName) {
|
|
6
|
+
const envDir = join(process.cwd(), '.ranger', envName);
|
|
7
|
+
const configPath = join(envDir, 'config.json');
|
|
8
|
+
const authPath = join(envDir, 'auth.json');
|
|
9
|
+
// Check if env exists
|
|
10
|
+
if (!existsSync(envDir)) {
|
|
11
|
+
console.error(`\nEnvironment "${envName}" not found.`);
|
|
12
|
+
console.error(` Run first: ranger add env ${envName}`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
// Check if config exists
|
|
16
|
+
if (!existsSync(configPath)) {
|
|
17
|
+
console.error(`\nConfig not found at ${configPath}`);
|
|
18
|
+
console.error(` Run first: ranger add env ${envName}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
// Load existing config
|
|
22
|
+
const configContent = await readFile(configPath, 'utf-8');
|
|
23
|
+
const config = JSON.parse(configContent);
|
|
24
|
+
// Check if existing auth state exists
|
|
25
|
+
const hasExistingAuth = existsSync(authPath);
|
|
26
|
+
console.log('\n📋 Authentication Update');
|
|
27
|
+
console.log(' A browser will open with your existing session (if any).');
|
|
28
|
+
console.log(' Update your authentication as needed.');
|
|
29
|
+
console.log(' When you are done, close the browser.\n');
|
|
30
|
+
// Launch Playwright browser with existing storage state if available
|
|
31
|
+
const browser = await chromium.launch({ headless: false });
|
|
32
|
+
const contextOptions = hasExistingAuth ? { storageState: authPath } : {};
|
|
33
|
+
const context = await browser.newContext(contextOptions);
|
|
34
|
+
const page = await context.newPage();
|
|
35
|
+
await page.goto('about:blank');
|
|
36
|
+
// Wait for browser to be closed by user
|
|
37
|
+
await new Promise((resolve) => {
|
|
38
|
+
page.on('close', async () => {
|
|
39
|
+
try {
|
|
40
|
+
console.log('Saving authentication state...');
|
|
41
|
+
await context.storageState({ path: authPath });
|
|
42
|
+
resolve();
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Context may already be closed
|
|
46
|
+
resolve();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
await browser.close();
|
|
51
|
+
console.log(`\n✓ Auth state saved to: ${authPath}`);
|
|
52
|
+
// Update config to reference the auth file
|
|
53
|
+
config.browser = config.browser || {};
|
|
54
|
+
config.browser.contextOptions = config.browser.contextOptions || {};
|
|
55
|
+
config.browser.contextOptions.storageState = authPath;
|
|
56
|
+
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
57
|
+
console.log(`✓ Updated config: ${configPath}`);
|
|
58
|
+
console.log(`\n✅ Environment "${envName}" authentication updated`);
|
|
59
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { loadMcpConfig, saveMcpConfig, setRangerBrowser, } from './utils/mcpConfig.js';
|
|
4
|
+
import { installAgent } from './utils/agents.js';
|
|
5
|
+
export async function useEnv(envName) {
|
|
6
|
+
const envDir = join(process.cwd(), '.ranger', envName);
|
|
7
|
+
const configPath = join(envDir, 'config.json');
|
|
8
|
+
// Check env exists
|
|
9
|
+
if (!existsSync(envDir)) {
|
|
10
|
+
console.error(`\n❌ Environment "${envName}" not found.`);
|
|
11
|
+
console.error(` Run first: ranger add env ${envName}`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
// Check config exists
|
|
15
|
+
if (!existsSync(configPath)) {
|
|
16
|
+
console.error(`\n❌ Config not found at ${configPath}`);
|
|
17
|
+
console.error(` Run first: ranger add env ${envName}`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
// Load and update MCP config
|
|
21
|
+
const mcpConfig = await loadMcpConfig();
|
|
22
|
+
setRangerBrowser(mcpConfig, configPath);
|
|
23
|
+
await saveMcpConfig(mcpConfig);
|
|
24
|
+
// Install browser-based agents if they don't exist
|
|
25
|
+
await installAgent('quality-advocate');
|
|
26
|
+
await installAgent('bug-basher');
|
|
27
|
+
console.log(`\n✅ Now using environment "${envName}"`);
|
|
28
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { mkdir, readdir, copyFile } from 'fs/promises';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
function getSourceAgentsDir() {
|
|
8
|
+
// Check multiple possible locations for the agents directory
|
|
9
|
+
const possiblePaths = [
|
|
10
|
+
join(__dirname, '..', '..', 'agents'),
|
|
11
|
+
join(__dirname, '..', '..', '..', 'agents'),
|
|
12
|
+
];
|
|
13
|
+
for (const p of possiblePaths) {
|
|
14
|
+
if (existsSync(p)) {
|
|
15
|
+
return p;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return possiblePaths[0];
|
|
19
|
+
}
|
|
20
|
+
export async function installAgent(agentName) {
|
|
21
|
+
const claudeAgentsDir = join(process.cwd(), '.claude', 'agents');
|
|
22
|
+
if (!existsSync(claudeAgentsDir)) {
|
|
23
|
+
await mkdir(claudeAgentsDir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
const sourceAgentsDir = getSourceAgentsDir();
|
|
26
|
+
try {
|
|
27
|
+
const agentFiles = await readdir(sourceAgentsDir);
|
|
28
|
+
const agentFile = agentFiles.find((f) => f.includes(agentName));
|
|
29
|
+
if (agentFile) {
|
|
30
|
+
const sourcePath = join(sourceAgentsDir, agentFile);
|
|
31
|
+
const targetPath = join(claudeAgentsDir, agentFile);
|
|
32
|
+
await copyFile(sourcePath, targetPath);
|
|
33
|
+
console.log(`✓ Added agent: ${agentFile}`);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.warn(`Warning: ${agentName} agent not found in source directory`);
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.error('Error copying agent file:', error);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
export function getMcpConfigPath() {
|
|
5
|
+
return join(process.cwd(), '.mcp.json');
|
|
6
|
+
}
|
|
7
|
+
export async function loadMcpConfig() {
|
|
8
|
+
const mcpConfigPath = getMcpConfigPath();
|
|
9
|
+
if (existsSync(mcpConfigPath)) {
|
|
10
|
+
try {
|
|
11
|
+
const existingConfig = await readFile(mcpConfigPath, 'utf-8');
|
|
12
|
+
return JSON.parse(existingConfig);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// If parse fails, start fresh
|
|
16
|
+
return { mcpServers: {} };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return { mcpServers: {} };
|
|
20
|
+
}
|
|
21
|
+
export async function saveMcpConfig(config) {
|
|
22
|
+
const mcpConfigPath = getMcpConfigPath();
|
|
23
|
+
await writeFile(mcpConfigPath, JSON.stringify(config, null, 2));
|
|
24
|
+
}
|
|
25
|
+
export function hasRangerServer(config) {
|
|
26
|
+
return !!config.mcpServers.ranger;
|
|
27
|
+
}
|
|
28
|
+
export function setRangerServer(config, token, serverUrl) {
|
|
29
|
+
config.mcpServers.ranger = {
|
|
30
|
+
type: 'http',
|
|
31
|
+
url: `${serverUrl}/mcp`,
|
|
32
|
+
headers: {
|
|
33
|
+
Authorization: `Bearer ${token}`,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function setRangerBrowser(config, configPath) {
|
|
38
|
+
config.mcpServers['ranger-browser'] = {
|
|
39
|
+
command: 'npx',
|
|
40
|
+
args: [
|
|
41
|
+
'@ranger-testing/playwright',
|
|
42
|
+
'run-mcp-server',
|
|
43
|
+
'--config',
|
|
44
|
+
configPath,
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { chromium } from 'playwright';
|
|
2
|
+
async function main() {
|
|
3
|
+
const browser = await chromium.launch({ headless: false });
|
|
4
|
+
const context = await browser.newContext({
|
|
5
|
+
storageState: '/Users/adwithmukherjee/dev/lavender-core/lavender-core/.ranger/debug-tool/local/auth.json',
|
|
6
|
+
});
|
|
7
|
+
const page = await context.newPage();
|
|
8
|
+
await page.goto('http://localhost:3000');
|
|
9
|
+
// Keep browser open
|
|
10
|
+
console.log('Browser opened. Press Ctrl+C to close.');
|
|
11
|
+
await new Promise(() => { });
|
|
12
|
+
}
|
|
13
|
+
main().catch(console.error);
|
package/package.json
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ranger-testing/ranger-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
|
-
"ranger": "./build/cli.js"
|
|
6
|
+
"ranger": "./build/cli.js",
|
|
7
|
+
"ranger-dev": "./build/cli.js"
|
|
7
8
|
},
|
|
8
9
|
"scripts": {
|
|
9
|
-
"build": "tsc && chmod 755 build/cli.js",
|
|
10
|
-
"dev": "tsx cli.ts"
|
|
10
|
+
"build": "tsc && cp -r src/agents build/ && chmod 755 build/cli.js",
|
|
11
|
+
"dev": "tsx src/cli.ts"
|
|
11
12
|
},
|
|
12
|
-
"files": ["build"
|
|
13
|
+
"files": ["build"],
|
|
13
14
|
"dependencies": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
15
|
+
"dotenv": "^16.4.5",
|
|
16
|
+
"inquirer": "^9.2.12",
|
|
17
|
+
"playwright": "^1.40.0",
|
|
18
|
+
"yargs": "^17.7.2",
|
|
19
|
+
"zod": "^3.23.8"
|
|
16
20
|
},
|
|
17
21
|
"devDependencies": {
|
|
22
|
+
"@types/inquirer": "^9.0.7",
|
|
18
23
|
"@types/node": "^22.0.0",
|
|
19
24
|
"@types/yargs": "^17.0.32",
|
|
20
25
|
"typescript": "^5.0.0"
|
|
File without changes
|
|
File without changes
|