@ranger-testing/ranger-cli 1.0.2 → 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.
@@ -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,45 +1,46 @@
1
1
  #!/usr/bin/env node
2
2
  import yargs from 'yargs/yargs';
3
- import { addEnv, start, useEnv } from './commands/index.js';
3
+ import { addEnv, start, useEnv, updateEnv } from './commands/index.js';
4
4
  import dotenv from 'dotenv';
5
5
  dotenv.config();
6
6
  // Setup yargs CLI
7
7
  yargs(process.argv.slice(2))
8
8
  .version('1.0.0')
9
- .command('add env <app-name> <env-type>', 'Add environment configuration', (yargs) => {
10
- return yargs
11
- .positional('app-name', {
9
+ .command('add env <env-name>', 'Add environment configuration', (yargs) => {
10
+ return yargs.positional('env-name', {
12
11
  type: 'string',
13
- description: 'Name of the application',
12
+ description: 'Name of the environment (e.g., local, staging, prod)',
14
13
  demandOption: true,
15
- })
16
- .positional('env-type', {
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', {
17
20
  type: 'string',
18
- description: 'Environment type (local or ci)',
19
- choices: ['local', 'ci'],
21
+ description: 'Name of the environment',
20
22
  demandOption: true,
21
23
  });
22
24
  }, async (argv) => {
23
- await addEnv(argv['app-name'], argv['env-type']);
25
+ await useEnv(argv['env-name']);
24
26
  })
25
- .command('use <app-name> <env-type>', 'Switch to using a specific environment', (yargs) => {
26
- return yargs
27
- .positional('app-name', {
27
+ .command('update env <env-name>', 'Update authentication for an existing environment', (yargs) => {
28
+ return yargs.positional('env-name', {
28
29
  type: 'string',
29
- description: 'Name of the application',
30
+ description: 'Name of the environment to update',
30
31
  demandOption: true,
31
- })
32
- .positional('env-type', {
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', {
33
38
  type: 'string',
34
- description: 'Environment type',
35
- choices: ['local', 'ci'],
39
+ description: 'Ranger API token with MCP access',
36
40
  demandOption: true,
37
41
  });
38
42
  }, async (argv) => {
39
- await useEnv(argv['app-name'], argv['env-type']);
40
- })
41
- .command('start', 'Initialize Ranger in your project', () => { }, async () => {
42
- await start();
43
+ await start(argv.token);
43
44
  })
44
45
  .demandCommand(1, 'You must specify a command')
45
46
  .help()
@@ -3,23 +3,17 @@ import { join } from 'path';
3
3
  import { existsSync } from 'fs';
4
4
  import inquirer from 'inquirer';
5
5
  import { chromium } from 'playwright';
6
- import { loadMcpConfig, saveMcpConfig, hasRangerServer, setRangerServer, setRangerBrowser, } from './utils/mcpConfig.js';
6
+ import { loadMcpConfig, saveMcpConfig, hasRangerServer, setRangerBrowser, } from './utils/mcpConfig.js';
7
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
- }
8
+ export async function addEnv(envName) {
9
+ const envDir = join(process.cwd(), '.ranger', envName);
16
10
  // Check if env already exists
17
11
  if (existsSync(envDir)) {
18
12
  const { overwrite } = await inquirer.prompt([
19
13
  {
20
14
  type: 'confirm',
21
15
  name: 'overwrite',
22
- message: `Environment "${envType}" already exists. Overwrite?`,
16
+ message: `Environment "${envName}" already exists. Overwrite?`,
23
17
  default: false,
24
18
  },
25
19
  ]);
@@ -28,23 +22,6 @@ export async function addEnv(appName, envType) {
28
22
  return;
29
23
  }
30
24
  }
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
25
  // Prompt for auth requirement
49
26
  const { requiresAuth } = await inquirer.prompt([
50
27
  {
@@ -66,7 +43,7 @@ export async function addEnv(appName, envType) {
66
43
  const browser = await chromium.launch({ headless: false });
67
44
  const context = await browser.newContext();
68
45
  const page = await context.newPage();
69
- await page.goto(targetUrl);
46
+ await page.goto('about:blank');
70
47
  // Wait for browser to be closed by user
71
48
  storageStatePath = await new Promise((resolve) => {
72
49
  // Save storage state when page closes (before context is destroyed)
@@ -86,8 +63,7 @@ export async function addEnv(appName, envType) {
86
63
  }
87
64
  // Write config.json
88
65
  const config = {
89
- type: envType,
90
- targetUrl,
66
+ name: envName,
91
67
  browser: {
92
68
  isolated: true,
93
69
  browserName: 'chromium',
@@ -109,54 +85,19 @@ export async function addEnv(appName, envType) {
109
85
  console.log(`✓ Created config: ${configPath}`);
110
86
  // Load existing .mcp.json or create new one
111
87
  const mcpConfig = await loadMcpConfig();
112
- // Only prompt for token if ranger server doesn't exist
88
+ // Check if ranger server is configured
113
89
  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);
90
+ console.error('\nRanger Tests MCP is not initialized.');
91
+ console.error(' To initialize, run: ranger start <token>');
92
+ process.exit(1);
153
93
  }
154
- // Always update ranger-browser with the config path
94
+ // Update ranger-browser with the config path
155
95
  setRangerBrowser(mcpConfig, configPath);
156
96
  await saveMcpConfig(mcpConfig);
157
97
  console.log(`✓ Updated .mcp.json configuration`);
158
98
  // Copy browser-based agents to .claude/agents
159
99
  await installAgent('quality-advocate');
160
100
  await installAgent('bug-basher');
161
- console.log(`\n✅ Environment "${envType}" configured for app "${appName}"`);
101
+ await installAgent('ui-verifier');
102
+ console.log(`\n✅ Environment "${envName}" configured`);
162
103
  }
@@ -1,3 +1,4 @@
1
1
  export { addEnv } from './addEnv.js';
2
2
  export { start } from './start.js';
3
3
  export { useEnv } from './useEnv.js';
4
+ export { updateEnv } from './updateEnv.js';
@@ -1,26 +1,10 @@
1
1
  import { mkdir, readFile, appendFile, writeFile } from 'fs/promises';
2
2
  import { join } from 'path';
3
3
  import { existsSync } from 'fs';
4
- import inquirer from 'inquirer';
5
4
  import { loadMcpConfig, saveMcpConfig, setRangerServer, } from './utils/mcpConfig.js';
6
5
  import { installAgent } from './utils/agents.js';
7
- export async function start() {
6
+ export async function start(token) {
8
7
  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
8
  // Validate API token
25
9
  const mcpServerUrl = process.env.MCP_SERVER_URL ||
26
10
  'https://mcp-server-301751771437.us-central1.run.app';
@@ -83,6 +67,6 @@ export async function start() {
83
67
  // Log next steps
84
68
  console.log('\n✅ Ranger setup complete!\n');
85
69
  console.log('Next steps:');
86
- console.log(' Add an environment for your app:');
87
- console.log(' ranger add env <app-name> local\n');
70
+ console.log(' Add an environment:');
71
+ console.log(' ranger add env local\n');
88
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
+ }
@@ -2,26 +2,19 @@ import { join } from 'path';
2
2
  import { existsSync } from 'fs';
3
3
  import { loadMcpConfig, saveMcpConfig, setRangerBrowser, } from './utils/mcpConfig.js';
4
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);
5
+ export async function useEnv(envName) {
6
+ const envDir = join(process.cwd(), '.ranger', envName);
8
7
  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
8
  // Check env exists
16
9
  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}`);
10
+ console.error(`\n❌ Environment "${envName}" not found.`);
11
+ console.error(` Run first: ranger add env ${envName}`);
19
12
  process.exit(1);
20
13
  }
21
14
  // Check config exists
22
15
  if (!existsSync(configPath)) {
23
16
  console.error(`\n❌ Config not found at ${configPath}`);
24
- console.error(` Run first: ranger-dev add env ${appName} ${envType}`);
17
+ console.error(` Run first: ranger add env ${envName}`);
25
18
  process.exit(1);
26
19
  }
27
20
  // Load and update MCP config
@@ -31,5 +24,5 @@ export async function useEnv(appName, envType) {
31
24
  // Install browser-based agents if they don't exist
32
25
  await installAgent('quality-advocate');
33
26
  await installAgent('bug-basher');
34
- console.log(`\n✅ Now using environment "${envType}" for app "${appName}"`);
27
+ console.log(`\n✅ Now using environment "${envName}"`);
35
28
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ranger-testing/ranger-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "ranger": "./build/cli.js",