@ranger-testing/ranger-cli 1.0.1 → 1.0.2
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/cli.js +28 -113
- package/build/commands/addApp.js +21 -0
- package/build/commands/addEnv.js +162 -0
- package/build/commands/index.js +3 -0
- package/build/commands/initAgents.js +84 -0
- package/build/commands/start.js +88 -0
- package/build/commands/useEnv.js +35 -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?"
|
package/build/cli.js
CHANGED
|
@@ -1,130 +1,45 @@
|
|
|
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 } 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('
|
|
9
|
+
.command('add env <app-name> <env-type>', 'Add environment configuration', (yargs) => {
|
|
109
10
|
return yargs
|
|
110
|
-
.positional('
|
|
11
|
+
.positional('app-name', {
|
|
111
12
|
type: 'string',
|
|
112
|
-
description: '
|
|
113
|
-
|
|
13
|
+
description: 'Name of the application',
|
|
14
|
+
demandOption: true,
|
|
114
15
|
})
|
|
115
|
-
.
|
|
116
|
-
alias: 't',
|
|
16
|
+
.positional('env-type', {
|
|
117
17
|
type: 'string',
|
|
118
|
-
description: '
|
|
18
|
+
description: 'Environment type (local or ci)',
|
|
19
|
+
choices: ['local', 'ci'],
|
|
20
|
+
demandOption: true,
|
|
21
|
+
});
|
|
22
|
+
}, async (argv) => {
|
|
23
|
+
await addEnv(argv['app-name'], argv['env-type']);
|
|
24
|
+
})
|
|
25
|
+
.command('use <app-name> <env-type>', 'Switch to using a specific environment', (yargs) => {
|
|
26
|
+
return yargs
|
|
27
|
+
.positional('app-name', {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'Name of the application',
|
|
119
30
|
demandOption: true,
|
|
120
31
|
})
|
|
121
|
-
.
|
|
122
|
-
alias: 'u',
|
|
32
|
+
.positional('env-type', {
|
|
123
33
|
type: 'string',
|
|
124
|
-
description: '
|
|
34
|
+
description: 'Environment type',
|
|
35
|
+
choices: ['local', 'ci'],
|
|
36
|
+
demandOption: true,
|
|
125
37
|
});
|
|
126
38
|
}, async (argv) => {
|
|
127
|
-
await
|
|
39
|
+
await useEnv(argv['app-name'], argv['env-type']);
|
|
40
|
+
})
|
|
41
|
+
.command('start', 'Initialize Ranger in your project', () => { }, async () => {
|
|
42
|
+
await start();
|
|
128
43
|
})
|
|
129
44
|
.demandCommand(1, 'You must specify a command')
|
|
130
45
|
.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,162 @@
|
|
|
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, setRangerServer, setRangerBrowser, } from './utils/mcpConfig.js';
|
|
7
|
+
import { installAgent } from './utils/agents.js';
|
|
8
|
+
export async function addEnv(appName, envType) {
|
|
9
|
+
const appDir = join(process.cwd(), '.ranger', appName);
|
|
10
|
+
const envDir = join(appDir, envType);
|
|
11
|
+
// Create app directory if it doesn't exist
|
|
12
|
+
if (!existsSync(appDir)) {
|
|
13
|
+
await mkdir(appDir, { recursive: true });
|
|
14
|
+
console.log(`✓ Created app directory: ${appDir}`);
|
|
15
|
+
}
|
|
16
|
+
// Check if env already exists
|
|
17
|
+
if (existsSync(envDir)) {
|
|
18
|
+
const { overwrite } = await inquirer.prompt([
|
|
19
|
+
{
|
|
20
|
+
type: 'confirm',
|
|
21
|
+
name: 'overwrite',
|
|
22
|
+
message: `Environment "${envType}" already exists. Overwrite?`,
|
|
23
|
+
default: false,
|
|
24
|
+
},
|
|
25
|
+
]);
|
|
26
|
+
if (!overwrite) {
|
|
27
|
+
console.log('Aborted.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Prompt for target URL
|
|
32
|
+
const { targetUrl } = await inquirer.prompt([
|
|
33
|
+
{
|
|
34
|
+
type: 'input',
|
|
35
|
+
name: 'targetUrl',
|
|
36
|
+
message: 'What is the target URL for this environment?',
|
|
37
|
+
validate: (input) => {
|
|
38
|
+
try {
|
|
39
|
+
new URL(input);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return 'Please enter a valid URL (e.g., https://example.com)';
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
// Prompt for auth requirement
|
|
49
|
+
const { requiresAuth } = await inquirer.prompt([
|
|
50
|
+
{
|
|
51
|
+
type: 'confirm',
|
|
52
|
+
name: 'requiresAuth',
|
|
53
|
+
message: 'Does this application require authentication?',
|
|
54
|
+
default: false,
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
// Create env directory
|
|
58
|
+
await mkdir(envDir, { recursive: true });
|
|
59
|
+
const authPath = join(envDir, 'auth.json');
|
|
60
|
+
let storageStatePath = undefined;
|
|
61
|
+
if (requiresAuth) {
|
|
62
|
+
console.log('\n📋 Authentication Setup');
|
|
63
|
+
console.log(' A browser will open. Please log in to your application.');
|
|
64
|
+
console.log(' When you are done logging in, close the browser.\n');
|
|
65
|
+
// Launch Playwright browser
|
|
66
|
+
const browser = await chromium.launch({ headless: false });
|
|
67
|
+
const context = await browser.newContext();
|
|
68
|
+
const page = await context.newPage();
|
|
69
|
+
await page.goto(targetUrl);
|
|
70
|
+
// Wait for browser to be closed by user
|
|
71
|
+
storageStatePath = await new Promise((resolve) => {
|
|
72
|
+
// Save storage state when page closes (before context is destroyed)
|
|
73
|
+
page.on('close', async () => {
|
|
74
|
+
try {
|
|
75
|
+
console.log('Saving authentication state...');
|
|
76
|
+
await context.storageState({ path: authPath });
|
|
77
|
+
resolve(authPath);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Context may already be closed
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
await browser.close();
|
|
85
|
+
console.log(`\n✓ Auth state saved to: ${authPath}`);
|
|
86
|
+
}
|
|
87
|
+
// Write config.json
|
|
88
|
+
const config = {
|
|
89
|
+
type: envType,
|
|
90
|
+
targetUrl,
|
|
91
|
+
browser: {
|
|
92
|
+
isolated: true,
|
|
93
|
+
browserName: 'chromium',
|
|
94
|
+
launchOptions: {
|
|
95
|
+
headless: false,
|
|
96
|
+
},
|
|
97
|
+
contextOptions: {
|
|
98
|
+
permissions: ['clipboard-read', 'clipboard-write'],
|
|
99
|
+
extraHTTPHeaders: {},
|
|
100
|
+
storageState: storageStatePath || undefined,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
capabilities: ['core', 'tabs', 'install', 'pdf', 'vision'],
|
|
104
|
+
imageResponses: 'allow',
|
|
105
|
+
saveTrace: true,
|
|
106
|
+
};
|
|
107
|
+
const configPath = join(envDir, 'config.json');
|
|
108
|
+
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
109
|
+
console.log(`✓ Created config: ${configPath}`);
|
|
110
|
+
// Load existing .mcp.json or create new one
|
|
111
|
+
const mcpConfig = await loadMcpConfig();
|
|
112
|
+
// Only prompt for token if ranger server doesn't exist
|
|
113
|
+
if (!hasRangerServer(mcpConfig)) {
|
|
114
|
+
const { token } = await inquirer.prompt([
|
|
115
|
+
{
|
|
116
|
+
type: 'password',
|
|
117
|
+
name: 'token',
|
|
118
|
+
message: 'Enter your Ranger API token:',
|
|
119
|
+
mask: '*',
|
|
120
|
+
validate: (input) => {
|
|
121
|
+
if (!input || input.trim().length === 0) {
|
|
122
|
+
return 'API token is required';
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
]);
|
|
128
|
+
// Validate API token
|
|
129
|
+
const mcpServerUrl = process.env.MCP_SERVER_URL ||
|
|
130
|
+
'https://mcp-server-301751771437.us-central1.run.app';
|
|
131
|
+
console.log('Validating API token...');
|
|
132
|
+
try {
|
|
133
|
+
const response = await fetch(`${mcpServerUrl}/me`, {
|
|
134
|
+
method: 'GET',
|
|
135
|
+
headers: {
|
|
136
|
+
Authorization: `Bearer ${token}`,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
console.error(`\n❌ Authentication failed: ${response.status} ${response.statusText}`);
|
|
141
|
+
console.error('Please check your API token and try again.');
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
console.log('✓ API token validated successfully');
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
console.error('\n❌ Failed to connect to MCP server:', error instanceof Error ? error.message : error);
|
|
148
|
+
console.error('Please check your network connection and server URL.');
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
// Add ranger server config
|
|
152
|
+
setRangerServer(mcpConfig, token, mcpServerUrl);
|
|
153
|
+
}
|
|
154
|
+
// Always update ranger-browser with the config path
|
|
155
|
+
setRangerBrowser(mcpConfig, configPath);
|
|
156
|
+
await saveMcpConfig(mcpConfig);
|
|
157
|
+
console.log(`✓ Updated .mcp.json configuration`);
|
|
158
|
+
// Copy browser-based agents to .claude/agents
|
|
159
|
+
await installAgent('quality-advocate');
|
|
160
|
+
await installAgent('bug-basher');
|
|
161
|
+
console.log(`\n✅ Environment "${envType}" configured for app "${appName}"`);
|
|
162
|
+
}
|
|
@@ -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,88 @@
|
|
|
1
|
+
import { mkdir, readFile, appendFile, writeFile } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { loadMcpConfig, saveMcpConfig, setRangerServer, } from './utils/mcpConfig.js';
|
|
6
|
+
import { installAgent } from './utils/agents.js';
|
|
7
|
+
export async function start() {
|
|
8
|
+
console.log('\n🚀 Ranger Setup\n');
|
|
9
|
+
// Prompt for API token
|
|
10
|
+
const { token } = await inquirer.prompt([
|
|
11
|
+
{
|
|
12
|
+
type: 'password',
|
|
13
|
+
name: 'token',
|
|
14
|
+
message: 'Enter your Ranger API token:',
|
|
15
|
+
mask: '*',
|
|
16
|
+
validate: (input) => {
|
|
17
|
+
if (!input || input.trim().length === 0) {
|
|
18
|
+
return 'API token is required';
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
]);
|
|
24
|
+
// Validate API token
|
|
25
|
+
const mcpServerUrl = process.env.MCP_SERVER_URL ||
|
|
26
|
+
'https://mcp-server-301751771437.us-central1.run.app';
|
|
27
|
+
console.log('Validating API token...');
|
|
28
|
+
try {
|
|
29
|
+
const response = await fetch(`${mcpServerUrl}/me`, {
|
|
30
|
+
method: 'GET',
|
|
31
|
+
headers: {
|
|
32
|
+
Authorization: `Bearer ${token}`,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
console.error(`\n❌ Authentication failed: ${response.status} ${response.statusText}`);
|
|
37
|
+
console.error('Please check your API token and try again.');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
console.log('✓ API token validated successfully');
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error('\n❌ Failed to connect to MCP server:', error instanceof Error ? error.message : error);
|
|
44
|
+
console.error('Please check your network connection and server URL.');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
// Load or create .mcp.json and set ranger server
|
|
48
|
+
const mcpConfig = await loadMcpConfig();
|
|
49
|
+
setRangerServer(mcpConfig, token, mcpServerUrl);
|
|
50
|
+
await saveMcpConfig(mcpConfig);
|
|
51
|
+
console.log('✓ Created/updated .mcp.json with Ranger MCP server');
|
|
52
|
+
// Create .ranger/ directory
|
|
53
|
+
const rangerDir = join(process.cwd(), '.ranger');
|
|
54
|
+
if (!existsSync(rangerDir)) {
|
|
55
|
+
await mkdir(rangerDir, { recursive: true });
|
|
56
|
+
console.log('✓ Created .ranger/ directory');
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.log('✓ .ranger/ directory already exists');
|
|
60
|
+
}
|
|
61
|
+
// Copy e2e-test-recommender agent to .claude/agents
|
|
62
|
+
await installAgent('e2e-test-recommender');
|
|
63
|
+
// Update .gitignore
|
|
64
|
+
const gitignorePath = join(process.cwd(), '.gitignore');
|
|
65
|
+
const gitignoreEntries = ['.ranger-mcp', '.ranger/**/local*'];
|
|
66
|
+
if (existsSync(gitignorePath)) {
|
|
67
|
+
const existingContent = await readFile(gitignorePath, 'utf-8');
|
|
68
|
+
const missingEntries = gitignoreEntries.filter((entry) => !existingContent.includes(entry));
|
|
69
|
+
if (missingEntries.length > 0) {
|
|
70
|
+
const newContent = '\n# Ranger\n' + missingEntries.join('\n') + '\n';
|
|
71
|
+
await appendFile(gitignorePath, newContent);
|
|
72
|
+
console.log('✓ Updated .gitignore with Ranger entries');
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.log('✓ .gitignore already contains Ranger entries');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const content = '# Ranger\n' + gitignoreEntries.join('\n') + '\n';
|
|
80
|
+
await writeFile(gitignorePath, content);
|
|
81
|
+
console.log('✓ Created .gitignore with Ranger entries');
|
|
82
|
+
}
|
|
83
|
+
// Log next steps
|
|
84
|
+
console.log('\n✅ Ranger setup complete!\n');
|
|
85
|
+
console.log('Next steps:');
|
|
86
|
+
console.log(' Add an environment for your app:');
|
|
87
|
+
console.log(' ranger add env <app-name> local\n');
|
|
88
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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(appName, envType) {
|
|
6
|
+
const appDir = join(process.cwd(), '.ranger', appName);
|
|
7
|
+
const envDir = join(appDir, envType);
|
|
8
|
+
const configPath = join(envDir, 'config.json');
|
|
9
|
+
// Check app exists
|
|
10
|
+
if (!existsSync(appDir)) {
|
|
11
|
+
console.error(`\n❌ App "${appName}" not found.`);
|
|
12
|
+
console.error(` Run first: ranger-dev add app ${appName}`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
// Check env exists
|
|
16
|
+
if (!existsSync(envDir)) {
|
|
17
|
+
console.error(`\n❌ Environment "${envType}" not found for app "${appName}".`);
|
|
18
|
+
console.error(` Run first: ranger-dev add env ${appName} ${envType}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
// Check config exists
|
|
22
|
+
if (!existsSync(configPath)) {
|
|
23
|
+
console.error(`\n❌ Config not found at ${configPath}`);
|
|
24
|
+
console.error(` Run first: ranger-dev add env ${appName} ${envType}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
// Load and update MCP config
|
|
28
|
+
const mcpConfig = await loadMcpConfig();
|
|
29
|
+
setRangerBrowser(mcpConfig, configPath);
|
|
30
|
+
await saveMcpConfig(mcpConfig);
|
|
31
|
+
// Install browser-based agents if they don't exist
|
|
32
|
+
await installAgent('quality-advocate');
|
|
33
|
+
await installAgent('bug-basher');
|
|
34
|
+
console.log(`\n✅ Now using environment "${envType}" for app "${appName}"`);
|
|
35
|
+
}
|
|
@@ -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.2",
|
|
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
|