@ranger-testing/ranger-cli 1.1.6 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -45
- package/build/cli.js +671 -291
- package/build/cli.js.map +1 -1
- package/build/commands/addEnv.js +1 -1
- package/build/commands/addEnv.js.map +1 -1
- package/build/commands/authEncrypt.js +5 -10
- package/build/commands/authEncrypt.js.map +1 -1
- package/build/commands/clean.js +1 -1
- package/build/commands/clean.js.map +1 -1
- package/build/commands/config.js +9 -15
- package/build/commands/config.js.map +1 -1
- package/build/commands/env.js +10 -13
- package/build/commands/env.js.map +1 -1
- package/build/commands/feature.js +138 -67
- package/build/commands/feature.js.map +1 -1
- package/build/commands/hook.js +9 -4
- package/build/commands/hook.js.map +1 -1
- package/build/commands/hooks/autoPrompt.js +32 -0
- package/build/commands/hooks/autoPrompt.js.map +1 -0
- package/build/commands/hooks/disable.js +8 -5
- package/build/commands/hooks/disable.js.map +1 -1
- package/build/commands/hooks/enable.js +16 -9
- package/build/commands/hooks/enable.js.map +1 -1
- package/build/commands/hooks/exitPlanMode.js +10 -10
- package/build/commands/hooks/exitPlanMode.js.map +1 -1
- package/build/commands/hooks/index.js +1 -0
- package/build/commands/hooks/index.js.map +1 -1
- package/build/commands/hooks/output.js +20 -2
- package/build/commands/hooks/output.js.map +1 -1
- package/build/commands/hooks/planReminder.js +9 -9
- package/build/commands/hooks/planReminder.js.map +1 -1
- package/build/commands/hooks/planStart.js +6 -6
- package/build/commands/hooks/planStart.js.map +1 -1
- package/build/commands/hooks/postEdit.js +6 -6
- package/build/commands/hooks/postEdit.js.map +1 -1
- package/build/commands/hooks/preCompact.js +5 -5
- package/build/commands/hooks/preCompact.js.map +1 -1
- package/build/commands/hooks/sessionEnd.js +8 -4
- package/build/commands/hooks/sessionEnd.js.map +1 -1
- package/build/commands/hooks/sessionStart.js +41 -25
- package/build/commands/hooks/sessionStart.js.map +1 -1
- package/build/commands/hooks/stopHook.js +30 -6
- package/build/commands/hooks/stopHook.js.map +1 -1
- package/build/commands/index.js +1 -2
- package/build/commands/index.js.map +1 -1
- package/build/commands/login.js +2 -5
- package/build/commands/login.js.map +1 -1
- package/build/commands/setupCi.js +189 -0
- package/build/commands/setupCi.js.map +1 -0
- package/build/commands/skillup.js +16 -68
- package/build/commands/skillup.js.map +1 -1
- package/build/commands/start.js +1 -1
- package/build/commands/start.js.map +1 -1
- package/build/commands/status.js +14 -13
- package/build/commands/status.js.map +1 -1
- package/build/commands/update.js +34 -5
- package/build/commands/update.js.map +1 -1
- package/build/commands/updateEnv.js +1 -1
- package/build/commands/updateEnv.js.map +1 -1
- package/build/commands/useEnv.js +1 -1
- package/build/commands/useEnv.js.map +1 -1
- package/build/commands/utils/activeProfile.js +76 -0
- package/build/commands/utils/activeProfile.js.map +1 -0
- package/build/commands/utils/browserSessionsApi.js +1 -1
- package/build/commands/utils/browserSessionsApi.js.map +1 -1
- package/build/commands/utils/desirePathLog.js +39 -34
- package/build/commands/utils/desirePathLog.js.map +1 -1
- package/build/commands/utils/deviceAuth.js +53 -5
- package/build/commands/utils/deviceAuth.js.map +1 -1
- package/build/commands/utils/environment.js +11 -12
- package/build/commands/utils/environment.js.map +1 -1
- package/build/commands/utils/featureApi.js +49 -46
- package/build/commands/utils/featureApi.js.map +1 -1
- package/build/commands/utils/featureReportGenerator.js +6 -6
- package/build/commands/utils/featureReportGenerator.js.map +1 -1
- package/build/commands/utils/keychain.js +1 -1
- package/build/commands/utils/localAgentInstallationsApi.js +1 -1
- package/build/commands/utils/profileMessages.js +8 -0
- package/build/commands/utils/profileMessages.js.map +1 -0
- package/build/commands/utils/profileSetupBanner.js +167 -0
- package/build/commands/utils/profileSetupBanner.js.map +1 -0
- package/build/commands/utils/retry.js +25 -0
- package/build/commands/utils/retry.js.map +1 -0
- package/build/commands/utils/sessionCache.js +17 -0
- package/build/commands/utils/sessionCache.js.map +1 -1
- package/build/commands/utils/settings.js +23 -2
- package/build/commands/utils/settings.js.map +1 -1
- package/build/commands/utils/skills.js +1 -1
- package/build/commands/utils/telemetry.js +254 -0
- package/build/commands/utils/telemetry.js.map +1 -0
- package/build/commands/utils/userApi.js +4 -4
- package/build/commands/utils/userApi.js.map +1 -1
- package/build/commands/verifyFeature.js +678 -407
- package/build/commands/verifyFeature.js.map +1 -1
- package/build/commands/verifyInBrowser.js +1 -1
- package/build/commands/verifyInBrowser.js.map +1 -1
- package/build/skills/ranger/SKILL.md +65 -64
- package/build/skills/ranger/create.md +31 -31
- package/build/skills/ranger/feedback.md +25 -17
- package/build/skills/ranger/start.md +37 -37
- package/build/skills/ranger/verify.md +59 -55
- package/package.json +1 -1
- package/scripts/postinstall.js +1 -1
- package/build/commands/dataMcpServer.js +0 -1
- package/build/commands/dataMcpServer.js.map +0 -1
- package/build/commands/utils/cliSecret.js +0 -1
- package/build/commands/utils/cliSecret.js.map +0 -1
- package/build/skills/bug-bash.md +0 -329
- package/build/skills/e2e-test-recommender.md +0 -168
package/build/cli.js
CHANGED
|
@@ -18,53 +18,282 @@ function findProjectRoot() {
|
|
|
18
18
|
// Load .env from project root (not just cwd)
|
|
19
19
|
dotenv.config({ path: join(findProjectRoot(), '.env') });
|
|
20
20
|
import yargs from 'yargs/yargs';
|
|
21
|
-
import { addEnv, clean, login, start, useEnv, updateEnv, update, skillup, envList, hook, } from './commands/index.js';
|
|
21
|
+
import { addEnv, clean, login, start, setupCi, useEnv, updateEnv, update, skillup, envList, hook, } from './commands/index.js';
|
|
22
22
|
import { authEncrypt } from './commands/authEncrypt.js';
|
|
23
23
|
import { status } from './commands/status.js';
|
|
24
24
|
import { configSet, configGet, configList, configUnset, } from './commands/config.js';
|
|
25
|
-
import { dataMcpServer } from './commands/dataMcpServer.js';
|
|
26
|
-
import { verifyInBrowser } from './commands/verifyInBrowser.js';
|
|
27
25
|
import { verifyFeature } from './commands/verifyFeature.js';
|
|
28
|
-
import { featureCreate, featureList, featureShow, featureResume,
|
|
29
|
-
import { generateMarkdownReport, collectScreenshots, } from './commands/utils/reportGenerator.js';
|
|
26
|
+
import { featureCreate, featureList, featureShow, featureResume, featureAddScenario, featureEditScenario, featureGetReview, featureDelete, featureRestore, } from './commands/feature.js';
|
|
30
27
|
import { logDesirePath, getErrorType, sanitizeArgs, } from './commands/utils/desirePathLog.js';
|
|
31
28
|
import { getCurrentVersion } from './commands/utils/version.js';
|
|
29
|
+
import { withTelemetry, getCurrentCollector, } from './commands/utils/telemetry.js';
|
|
30
|
+
// Capture unhandled rejections for telemetry
|
|
31
|
+
process.on('unhandledRejection', async (reason) => {
|
|
32
|
+
const collector = getCurrentCollector();
|
|
33
|
+
if (collector) {
|
|
34
|
+
await collector.trackCommandError(reason);
|
|
35
|
+
}
|
|
36
|
+
process.exitCode = 1;
|
|
37
|
+
});
|
|
38
|
+
const rawArgs = process.argv.slice(2);
|
|
39
|
+
const TOP_LEVEL_HELP = `Usage: ranger <command> [options]
|
|
40
|
+
|
|
41
|
+
Commands:
|
|
42
|
+
setup [token] Initialize Ranger in your project
|
|
43
|
+
login Re-authenticate without full setup
|
|
44
|
+
skillup Install Ranger skills for Claude Code
|
|
45
|
+
clean Remove Ranger artifacts from the project
|
|
46
|
+
status Show version, org, skills, and profile status
|
|
47
|
+
update Update Ranger CLI to the latest version
|
|
48
|
+
|
|
49
|
+
profile <command> Manage profiles (add/use/ls/update/config/encrypt-auth)
|
|
50
|
+
add <profile-name> Add profile (options: --ci, --skip-auth)
|
|
51
|
+
use <profile-name> Switch active profile
|
|
52
|
+
ls List profiles
|
|
53
|
+
update <profile-name> Re-capture auth for a profile
|
|
54
|
+
encrypt-auth <profile> Encrypt auth.json for safe git storage
|
|
55
|
+
config <command> Set/get/list/unset profile config
|
|
56
|
+
|
|
57
|
+
create <name> Create a feature review with scenarios
|
|
58
|
+
list List feature reviews
|
|
59
|
+
show [id] Show feature review details
|
|
60
|
+
resume [id] Resume a feature review
|
|
61
|
+
add-scenario <description> Add a scenario to the active feature review
|
|
62
|
+
edit-scenario <description> Edit a scenario description
|
|
63
|
+
get-review [id] Show reviewer feedback
|
|
64
|
+
delete [id] Soft delete a feature review
|
|
65
|
+
restore <id> Restore a soft-deleted feature review
|
|
66
|
+
|
|
67
|
+
go Verify a scenario in the browser
|
|
68
|
+
|
|
69
|
+
Run \`ranger <command> --help\` for details.`;
|
|
70
|
+
const PROFILE_HELP = `Usage: ranger profile <command> [options]
|
|
71
|
+
|
|
72
|
+
Commands:
|
|
73
|
+
add <profile-name> Add profile (options: --ci, --skip-auth)
|
|
74
|
+
use <profile-name> Switch active profile
|
|
75
|
+
ls List profiles
|
|
76
|
+
update <profile-name> Re-capture auth for a profile
|
|
77
|
+
encrypt-auth <profile> Encrypt auth.json for safe git storage
|
|
78
|
+
config <command> Profile config (set/get/list/unset)
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
ranger profile add local
|
|
82
|
+
ranger profile encrypt-auth ci
|
|
83
|
+
ranger profile config set local headless true`;
|
|
84
|
+
const PROFILE_CONFIG_HELP = `Usage: ranger profile config <command>
|
|
85
|
+
|
|
86
|
+
Commands:
|
|
87
|
+
set <profile> <key> <value> Set a config value
|
|
88
|
+
get <profile> <key> Get a config value
|
|
89
|
+
list <profile> List all config for a profile
|
|
90
|
+
unset <profile> <key> Remove a config value
|
|
91
|
+
|
|
92
|
+
Keys:
|
|
93
|
+
userAgent Browser user agent string
|
|
94
|
+
headless Run browser in headless mode (true/false)
|
|
95
|
+
storageState Path to auth state file (e.g., ./auth.json)
|
|
96
|
+
header.<name> Custom HTTP header (e.g., header.X-Test-Mode)
|
|
97
|
+
|
|
98
|
+
Examples:
|
|
99
|
+
ranger profile config set ci userAgent "Mozilla/5.0 (CI Bot)"
|
|
100
|
+
ranger profile config set ci headless true
|
|
101
|
+
ranger profile config set ci header.Authorization '\${AUTH_TOKEN}'`;
|
|
102
|
+
function warnRenamed(oldUsage, newUsage) {
|
|
103
|
+
console.error(`\n${oldUsage} is now ${newUsage}\n`);
|
|
104
|
+
}
|
|
105
|
+
function argUsed(flag) {
|
|
106
|
+
return rawArgs.some((arg) => arg === flag || arg.startsWith(`${flag}=`));
|
|
107
|
+
}
|
|
108
|
+
function warnFlagRenamed(commandPrefix, oldFlag, newFlag) {
|
|
109
|
+
if (argUsed(oldFlag)) {
|
|
110
|
+
warnRenamed(`${commandPrefix} ${oldFlag}`, `${commandPrefix} ${newFlag}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function runGoCommand(argv, isLegacy = false) {
|
|
114
|
+
if (isLegacy) {
|
|
115
|
+
warnRenamed('ranger verify-feature', 'ranger go');
|
|
116
|
+
}
|
|
117
|
+
warnFlagRenamed('ranger go', '--env', '--profile');
|
|
118
|
+
warnFlagRenamed('ranger go', '--task', '--notes');
|
|
119
|
+
warnFlagRenamed('ranger go', '--item', '--scenario');
|
|
120
|
+
const result = await verifyFeature({
|
|
121
|
+
profile: argv.profile ??
|
|
122
|
+
argv.env,
|
|
123
|
+
notes: argv.notes ??
|
|
124
|
+
argv.task,
|
|
125
|
+
scenario: argv.scenario ??
|
|
126
|
+
argv.item,
|
|
127
|
+
startPath: argv['start-path'],
|
|
128
|
+
debugOutcome: argv['debug-outcome'],
|
|
129
|
+
});
|
|
130
|
+
console.log('\n' + '='.repeat(60));
|
|
131
|
+
console.log(result.evaluation === 'verified'
|
|
132
|
+
? ' VERIFIED'
|
|
133
|
+
: result.evaluation === 'incomplete'
|
|
134
|
+
? ' INCOMPLETE'
|
|
135
|
+
: result.evaluation === 'partial'
|
|
136
|
+
? ' PARTIAL'
|
|
137
|
+
: result.evaluation === 'blocked'
|
|
138
|
+
? ' BLOCKED'
|
|
139
|
+
: ' FAILED');
|
|
140
|
+
console.log('='.repeat(60));
|
|
141
|
+
console.log(`Summary: ${result.summary}`);
|
|
142
|
+
console.log(`Evaluation: ${result.evaluation}`);
|
|
143
|
+
console.log(`Reason: ${result.evaluationReason}`);
|
|
144
|
+
if (result.issues?.length) {
|
|
145
|
+
console.log('\nIssues:');
|
|
146
|
+
result.issues.forEach((issue, i) => {
|
|
147
|
+
console.log(`\n${i + 1}. [${issue.severity}] ${issue.description}`);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
process.exit(result.evaluation === 'verified' ? 0 : 1);
|
|
151
|
+
}
|
|
32
152
|
// Setup yargs CLI
|
|
33
153
|
yargs(process.argv.slice(2))
|
|
154
|
+
.scriptName('ranger')
|
|
155
|
+
.usage(TOP_LEVEL_HELP)
|
|
34
156
|
.version(getCurrentVersion())
|
|
35
|
-
.command('
|
|
157
|
+
.command('create <name>', 'Create a new feature review with scenarios', (yargs) => {
|
|
36
158
|
return yargs
|
|
37
|
-
.positional('
|
|
159
|
+
.positional('name', {
|
|
38
160
|
type: 'string',
|
|
39
|
-
description: '
|
|
161
|
+
description: 'Feature review name',
|
|
40
162
|
demandOption: true,
|
|
41
163
|
})
|
|
42
|
-
.option('
|
|
164
|
+
.option('description', {
|
|
165
|
+
type: 'string',
|
|
166
|
+
alias: 'd',
|
|
167
|
+
description: 'Feature review description',
|
|
168
|
+
})
|
|
169
|
+
.option('scenario', {
|
|
170
|
+
type: 'array',
|
|
171
|
+
alias: 'c',
|
|
172
|
+
description: 'Scenarios (use multiple -c flags for multiple scenarios)',
|
|
173
|
+
})
|
|
174
|
+
.option('checklist', {
|
|
175
|
+
type: 'array',
|
|
176
|
+
hidden: true,
|
|
177
|
+
});
|
|
178
|
+
}, async (argv) => {
|
|
179
|
+
warnFlagRenamed('ranger create', '--checklist', '--scenario');
|
|
180
|
+
const scenarios = argv.scenario ||
|
|
181
|
+
argv.checklist;
|
|
182
|
+
await withTelemetry('create', () => featureCreate(argv.name, {
|
|
183
|
+
description: argv.description,
|
|
184
|
+
scenarios,
|
|
185
|
+
}));
|
|
186
|
+
})
|
|
187
|
+
.command('list', 'List all feature reviews', (yargs) => {
|
|
188
|
+
return yargs
|
|
189
|
+
.option('current-branch', {
|
|
43
190
|
type: 'boolean',
|
|
44
|
-
description: '
|
|
45
|
-
|
|
191
|
+
description: 'Filter to feature reviews for current git branch',
|
|
192
|
+
})
|
|
193
|
+
.option('limit', {
|
|
194
|
+
type: 'number',
|
|
195
|
+
alias: 'l',
|
|
196
|
+
description: 'Maximum number of feature reviews to return',
|
|
197
|
+
default: 10,
|
|
46
198
|
})
|
|
47
|
-
.option('
|
|
199
|
+
.option('offset', {
|
|
200
|
+
type: 'number',
|
|
201
|
+
alias: 'o',
|
|
202
|
+
description: 'Number of feature reviews to skip',
|
|
203
|
+
default: 0,
|
|
204
|
+
})
|
|
205
|
+
.option('include-deleted', {
|
|
48
206
|
type: 'boolean',
|
|
49
|
-
|
|
207
|
+
alias: 'd',
|
|
208
|
+
description: 'Include soft-deleted feature reviews',
|
|
50
209
|
default: false,
|
|
51
210
|
});
|
|
52
211
|
}, async (argv) => {
|
|
53
|
-
await
|
|
54
|
-
|
|
55
|
-
|
|
212
|
+
await withTelemetry('list', () => featureList({
|
|
213
|
+
currentBranch: argv['current-branch'],
|
|
214
|
+
limit: argv.limit,
|
|
215
|
+
offset: argv.offset,
|
|
216
|
+
includeDeleted: argv['include-deleted'],
|
|
217
|
+
}));
|
|
218
|
+
})
|
|
219
|
+
.command('show [id]', 'Show feature review details (uses active feature review if no id)', (yargs) => {
|
|
220
|
+
return yargs.positional('id', {
|
|
221
|
+
type: 'string',
|
|
222
|
+
description: 'Feature review ID',
|
|
56
223
|
});
|
|
224
|
+
}, async (argv) => {
|
|
225
|
+
await withTelemetry('show', () => featureShow(argv.id));
|
|
57
226
|
})
|
|
58
|
-
.command('
|
|
59
|
-
return yargs.positional('
|
|
227
|
+
.command('resume [id]', 'Find and use feature review matching current git context', (yargs) => {
|
|
228
|
+
return yargs.positional('id', {
|
|
60
229
|
type: 'string',
|
|
61
|
-
description: '
|
|
230
|
+
description: 'Feature review ID (optional - bypasses search/prompt)',
|
|
231
|
+
});
|
|
232
|
+
}, async (argv) => {
|
|
233
|
+
await withTelemetry('resume', () => featureResume(argv.id));
|
|
234
|
+
})
|
|
235
|
+
.command('add-scenario <description>', 'Add a scenario to the active feature review', (yargs) => {
|
|
236
|
+
return yargs
|
|
237
|
+
.positional('description', {
|
|
238
|
+
type: 'string',
|
|
239
|
+
description: 'Scenario description',
|
|
62
240
|
demandOption: true,
|
|
241
|
+
})
|
|
242
|
+
.option('id', {
|
|
243
|
+
type: 'string',
|
|
244
|
+
description: 'Feature review ID (uses active feature review if not provided)',
|
|
63
245
|
});
|
|
64
246
|
}, async (argv) => {
|
|
65
|
-
await
|
|
247
|
+
await withTelemetry('add-scenario', () => featureAddScenario(argv.description, argv.id));
|
|
66
248
|
})
|
|
67
|
-
.command('
|
|
249
|
+
.command('edit-scenario <description>', 'Edit a scenario description on the active feature review', (yargs) => {
|
|
250
|
+
return yargs
|
|
251
|
+
.positional('description', {
|
|
252
|
+
type: 'string',
|
|
253
|
+
description: 'New scenario description',
|
|
254
|
+
demandOption: true,
|
|
255
|
+
})
|
|
256
|
+
.option('scenario', {
|
|
257
|
+
type: 'number',
|
|
258
|
+
description: 'Scenario number to edit (1-based)',
|
|
259
|
+
demandOption: true,
|
|
260
|
+
})
|
|
261
|
+
.option('id', {
|
|
262
|
+
type: 'string',
|
|
263
|
+
description: 'Feature review ID (uses active feature review if not provided)',
|
|
264
|
+
});
|
|
265
|
+
}, async (argv) => {
|
|
266
|
+
await withTelemetry('edit-scenario', () => featureEditScenario(argv.description, {
|
|
267
|
+
id: argv.id,
|
|
268
|
+
scenario: argv.scenario,
|
|
269
|
+
}));
|
|
270
|
+
})
|
|
271
|
+
.command('get-review [id]', 'Show reviewer feedback (comments) for all scenarios', (yargs) => {
|
|
272
|
+
return yargs.positional('id', {
|
|
273
|
+
type: 'string',
|
|
274
|
+
description: 'Feature review ID (uses active feature review if not provided)',
|
|
275
|
+
});
|
|
276
|
+
}, async (argv) => {
|
|
277
|
+
await withTelemetry('get-review', () => featureGetReview(argv.id));
|
|
278
|
+
})
|
|
279
|
+
.command('delete [id]', 'Soft delete a feature review (uses active feature review if no id)', (yargs) => {
|
|
280
|
+
return yargs.positional('id', {
|
|
281
|
+
type: 'string',
|
|
282
|
+
description: 'Feature review ID',
|
|
283
|
+
});
|
|
284
|
+
}, async (argv) => {
|
|
285
|
+
await withTelemetry('delete', () => featureDelete(argv.id));
|
|
286
|
+
})
|
|
287
|
+
.command('restore <id>', 'Restore a soft-deleted feature review', (yargs) => {
|
|
288
|
+
return yargs.positional('id', {
|
|
289
|
+
type: 'string',
|
|
290
|
+
description: 'Feature review ID to restore',
|
|
291
|
+
demandOption: true,
|
|
292
|
+
});
|
|
293
|
+
}, async (argv) => {
|
|
294
|
+
await withTelemetry('restore', () => featureRestore(argv.id));
|
|
295
|
+
})
|
|
296
|
+
.command('setup [token]', 'Initialize Ranger in your project', (yargs) => {
|
|
68
297
|
return yargs
|
|
69
298
|
.positional('token', {
|
|
70
299
|
type: 'string',
|
|
@@ -76,22 +305,331 @@ yargs(process.argv.slice(2))
|
|
|
76
305
|
default: false,
|
|
77
306
|
});
|
|
78
307
|
}, async (argv) => {
|
|
79
|
-
await start(argv.token, {
|
|
308
|
+
await withTelemetry('start', (telemetry) => start(argv.token, {
|
|
80
309
|
skipChromium: argv['skip-chromium'],
|
|
310
|
+
}, telemetry));
|
|
311
|
+
})
|
|
312
|
+
.command('setup-ci <token>', 'Set up Ranger for CI (non-interactive)', (yargs) => {
|
|
313
|
+
return yargs
|
|
314
|
+
.positional('token', {
|
|
315
|
+
type: 'string',
|
|
316
|
+
description: 'Ranger API token',
|
|
317
|
+
demandOption: true,
|
|
318
|
+
})
|
|
319
|
+
.option('profile', {
|
|
320
|
+
type: 'string',
|
|
321
|
+
description: 'CI profile name (auto-detected if only one exists)',
|
|
322
|
+
})
|
|
323
|
+
.option('base-url', {
|
|
324
|
+
type: 'string',
|
|
325
|
+
description: 'Base URL for the app (creates/updates profile settings)',
|
|
326
|
+
})
|
|
327
|
+
.option('skip-chromium', {
|
|
328
|
+
type: 'boolean',
|
|
329
|
+
description: 'Skip Chromium browser installation',
|
|
330
|
+
default: false,
|
|
81
331
|
});
|
|
332
|
+
}, async (argv) => {
|
|
333
|
+
await withTelemetry('setup-ci', (telemetry) => setupCi(argv.token, {
|
|
334
|
+
profile: argv.profile,
|
|
335
|
+
baseUrl: argv['base-url'],
|
|
336
|
+
skipChromium: argv['skip-chromium'],
|
|
337
|
+
}, telemetry));
|
|
82
338
|
})
|
|
83
339
|
.command('login', 'Log in to Ranger via browser (re-authenticate without full setup)', () => { }, async () => {
|
|
84
|
-
await login();
|
|
340
|
+
await withTelemetry('login', () => login());
|
|
85
341
|
})
|
|
86
342
|
.command('skillup', 'Install Ranger skills for Claude Code', () => { }, async () => {
|
|
87
|
-
await skillup();
|
|
343
|
+
await withTelemetry('skillup', () => skillup());
|
|
344
|
+
})
|
|
345
|
+
.command('clean', 'Remove all Ranger artifacts from the project', () => { }, async () => {
|
|
346
|
+
await withTelemetry('clean', () => clean());
|
|
347
|
+
})
|
|
348
|
+
.command('profile', 'Manage profiles', (yargs) => {
|
|
349
|
+
return yargs
|
|
350
|
+
.usage(PROFILE_HELP)
|
|
351
|
+
.command('add <profile-name>', 'Add profile configuration', (yargs) => {
|
|
352
|
+
return yargs
|
|
353
|
+
.positional('profile-name', {
|
|
354
|
+
type: 'string',
|
|
355
|
+
description: 'Name of the profile (e.g., local, staging, prod)',
|
|
356
|
+
demandOption: true,
|
|
357
|
+
})
|
|
358
|
+
.option('ci', {
|
|
359
|
+
type: 'boolean',
|
|
360
|
+
description: 'Create CI profile (encrypted auth, committed to git)',
|
|
361
|
+
default: false,
|
|
362
|
+
})
|
|
363
|
+
.option('skip-auth', {
|
|
364
|
+
type: 'boolean',
|
|
365
|
+
description: 'Skip browser authentication (just save URL and settings)',
|
|
366
|
+
default: false,
|
|
367
|
+
});
|
|
368
|
+
}, async (argv) => {
|
|
369
|
+
await withTelemetry('profile add', (telemetry) => addEnv(argv['profile-name'], {
|
|
370
|
+
ci: argv.ci,
|
|
371
|
+
skipAuth: argv['skip-auth'],
|
|
372
|
+
}, telemetry));
|
|
373
|
+
})
|
|
374
|
+
.command('use <profile-name>', 'Switch to using a specific profile', (yargs) => {
|
|
375
|
+
return yargs.positional('profile-name', {
|
|
376
|
+
type: 'string',
|
|
377
|
+
description: 'Name of the profile',
|
|
378
|
+
demandOption: true,
|
|
379
|
+
});
|
|
380
|
+
}, async (argv) => {
|
|
381
|
+
await withTelemetry('profile use', () => useEnv(argv['profile-name']));
|
|
382
|
+
})
|
|
383
|
+
.command('encrypt-auth <profile>', 'Encrypt auth.json for a profile (allows committing to git)', (yargs) => {
|
|
384
|
+
return yargs.positional('profile', {
|
|
385
|
+
type: 'string',
|
|
386
|
+
description: 'Profile name',
|
|
387
|
+
demandOption: true,
|
|
388
|
+
});
|
|
389
|
+
}, async (argv) => {
|
|
390
|
+
await withTelemetry('profile encrypt-auth', () => authEncrypt(argv.profile));
|
|
391
|
+
})
|
|
392
|
+
.command('ls', 'List all profiles', () => { }, async () => {
|
|
393
|
+
await withTelemetry('profile ls', () => envList());
|
|
394
|
+
})
|
|
395
|
+
.command('update <profile-name>', 'Update authentication for an existing profile', (yargs) => {
|
|
396
|
+
return yargs.positional('profile-name', {
|
|
397
|
+
type: 'string',
|
|
398
|
+
description: 'Name of the profile to update',
|
|
399
|
+
demandOption: true,
|
|
400
|
+
});
|
|
401
|
+
}, async (argv) => {
|
|
402
|
+
await withTelemetry('profile update', (telemetry) => updateEnv(argv['profile-name'], telemetry));
|
|
403
|
+
})
|
|
404
|
+
.command('config', 'Manage profile configuration', (yargs) => {
|
|
405
|
+
return yargs
|
|
406
|
+
.usage(PROFILE_CONFIG_HELP)
|
|
407
|
+
.command('set <profile> <key> <value>', 'Set a config value', (yargs) => {
|
|
408
|
+
return yargs
|
|
409
|
+
.positional('profile', {
|
|
410
|
+
type: 'string',
|
|
411
|
+
description: 'Profile name',
|
|
412
|
+
demandOption: true,
|
|
413
|
+
})
|
|
414
|
+
.positional('key', {
|
|
415
|
+
type: 'string',
|
|
416
|
+
description: 'Config key (e.g., userAgent, header.X-Custom)',
|
|
417
|
+
demandOption: true,
|
|
418
|
+
})
|
|
419
|
+
.positional('value', {
|
|
420
|
+
type: 'string',
|
|
421
|
+
description: 'Config value',
|
|
422
|
+
demandOption: true,
|
|
423
|
+
});
|
|
424
|
+
}, async (argv) => {
|
|
425
|
+
await withTelemetry('profile config set', () => configSet(argv.profile, argv.key, argv.value));
|
|
426
|
+
})
|
|
427
|
+
.command('get <profile> <key>', 'Get a config value', (yargs) => {
|
|
428
|
+
return yargs
|
|
429
|
+
.positional('profile', {
|
|
430
|
+
type: 'string',
|
|
431
|
+
description: 'Profile name',
|
|
432
|
+
demandOption: true,
|
|
433
|
+
})
|
|
434
|
+
.positional('key', {
|
|
435
|
+
type: 'string',
|
|
436
|
+
description: 'Config key',
|
|
437
|
+
demandOption: true,
|
|
438
|
+
});
|
|
439
|
+
}, async (argv) => {
|
|
440
|
+
await withTelemetry('profile config get', () => configGet(argv.profile, argv.key));
|
|
441
|
+
})
|
|
442
|
+
.command('list <profile>', 'List all config for a profile', (yargs) => {
|
|
443
|
+
return yargs.positional('profile', {
|
|
444
|
+
type: 'string',
|
|
445
|
+
description: 'Profile name',
|
|
446
|
+
demandOption: true,
|
|
447
|
+
});
|
|
448
|
+
}, async (argv) => {
|
|
449
|
+
await withTelemetry('profile config list', () => configList(argv.profile));
|
|
450
|
+
})
|
|
451
|
+
.command('unset <profile> <key>', 'Remove a config value', (yargs) => {
|
|
452
|
+
return yargs
|
|
453
|
+
.positional('profile', {
|
|
454
|
+
type: 'string',
|
|
455
|
+
description: 'Profile name',
|
|
456
|
+
demandOption: true,
|
|
457
|
+
})
|
|
458
|
+
.positional('key', {
|
|
459
|
+
type: 'string',
|
|
460
|
+
description: 'Config key to remove',
|
|
461
|
+
demandOption: true,
|
|
462
|
+
});
|
|
463
|
+
}, async (argv) => {
|
|
464
|
+
await withTelemetry('profile config unset', () => configUnset(argv.profile, argv.key));
|
|
465
|
+
})
|
|
466
|
+
.demandCommand(1, 'You must specify a profile config subcommand');
|
|
467
|
+
})
|
|
468
|
+
.demandCommand(1, 'You must specify a profile subcommand');
|
|
469
|
+
})
|
|
470
|
+
.command('status', 'Show Ranger status (version, skills, profiles)', () => { }, async () => {
|
|
471
|
+
await withTelemetry('status', () => status());
|
|
472
|
+
})
|
|
473
|
+
.command('update', 'Update Ranger CLI to the latest version', () => { }, async () => {
|
|
474
|
+
await withTelemetry('update', () => update());
|
|
475
|
+
})
|
|
476
|
+
.command('go', 'Verify a scenario in the browser (requires active feature review)', (yargs) => {
|
|
477
|
+
return yargs
|
|
478
|
+
.option('profile', {
|
|
479
|
+
type: 'string',
|
|
480
|
+
description: 'Profile to use (defaults to active profile)',
|
|
481
|
+
})
|
|
482
|
+
.option('notes', {
|
|
483
|
+
type: 'string',
|
|
484
|
+
description: 'Notes for verification (defaults to scenario description)',
|
|
485
|
+
})
|
|
486
|
+
.option('scenario', {
|
|
487
|
+
type: 'number',
|
|
488
|
+
description: 'Scenario index (1-based)',
|
|
489
|
+
})
|
|
490
|
+
.option('start-path', {
|
|
491
|
+
type: 'string',
|
|
492
|
+
description: 'Path to start on (appended to base URL, e.g., /dashboard)',
|
|
493
|
+
})
|
|
494
|
+
.option('debug-outcome', {
|
|
495
|
+
type: 'string',
|
|
496
|
+
hidden: true,
|
|
497
|
+
choices: [
|
|
498
|
+
'verified',
|
|
499
|
+
'partial',
|
|
500
|
+
'blocked',
|
|
501
|
+
'failed',
|
|
502
|
+
'incomplete',
|
|
503
|
+
],
|
|
504
|
+
})
|
|
505
|
+
.option('env', {
|
|
506
|
+
type: 'string',
|
|
507
|
+
hidden: true,
|
|
508
|
+
})
|
|
509
|
+
.option('task', {
|
|
510
|
+
type: 'string',
|
|
511
|
+
hidden: true,
|
|
512
|
+
})
|
|
513
|
+
.option('item', {
|
|
514
|
+
type: 'number',
|
|
515
|
+
hidden: true,
|
|
516
|
+
});
|
|
517
|
+
}, async (argv) => {
|
|
518
|
+
await runGoCommand(argv);
|
|
519
|
+
})
|
|
520
|
+
// Legacy command shims (hidden from help)
|
|
521
|
+
.command('auth', false, (yargs) => {
|
|
522
|
+
return yargs
|
|
523
|
+
.command('encrypt <profile>', false, (yargs) => {
|
|
524
|
+
return yargs.positional('profile', {
|
|
525
|
+
type: 'string',
|
|
526
|
+
description: 'Profile name',
|
|
527
|
+
demandOption: true,
|
|
528
|
+
});
|
|
529
|
+
}, async (argv) => {
|
|
530
|
+
warnRenamed('ranger auth encrypt', 'ranger profile encrypt-auth');
|
|
531
|
+
await authEncrypt(argv.profile);
|
|
532
|
+
})
|
|
533
|
+
.demandCommand(1, 'You must specify an auth subcommand');
|
|
534
|
+
}, () => { })
|
|
535
|
+
.command('start [token]', false, (yargs) => {
|
|
536
|
+
return yargs
|
|
537
|
+
.positional('token', {
|
|
538
|
+
type: 'string',
|
|
539
|
+
description: 'Ranger API token (omit to log in via browser)',
|
|
540
|
+
})
|
|
541
|
+
.option('skip-chromium', {
|
|
542
|
+
type: 'boolean',
|
|
543
|
+
description: 'Skip Chromium browser check and installation',
|
|
544
|
+
default: false,
|
|
545
|
+
});
|
|
546
|
+
}, async (argv) => {
|
|
547
|
+
warnRenamed('ranger start', 'ranger setup');
|
|
548
|
+
await withTelemetry('start', (telemetry) => start(argv.token, {
|
|
549
|
+
skipChromium: argv['skip-chromium'],
|
|
550
|
+
}, telemetry));
|
|
551
|
+
})
|
|
552
|
+
.command('verify-feature', false, (yargs) => {
|
|
553
|
+
return yargs
|
|
554
|
+
.option('env', {
|
|
555
|
+
type: 'string',
|
|
556
|
+
description: 'Profile to use (defaults to active profile)',
|
|
557
|
+
})
|
|
558
|
+
.option('task', {
|
|
559
|
+
type: 'string',
|
|
560
|
+
description: 'Task description (defaults to scenario description)',
|
|
561
|
+
})
|
|
562
|
+
.option('item', {
|
|
563
|
+
type: 'number',
|
|
564
|
+
description: 'Scenario index (1-based)',
|
|
565
|
+
})
|
|
566
|
+
.option('profile', {
|
|
567
|
+
type: 'string',
|
|
568
|
+
hidden: true,
|
|
569
|
+
})
|
|
570
|
+
.option('notes', {
|
|
571
|
+
type: 'string',
|
|
572
|
+
hidden: true,
|
|
573
|
+
})
|
|
574
|
+
.option('scenario', {
|
|
575
|
+
type: 'number',
|
|
576
|
+
hidden: true,
|
|
577
|
+
})
|
|
578
|
+
.option('start-path', {
|
|
579
|
+
type: 'string',
|
|
580
|
+
description: 'Path to start on (appended to base URL, e.g., /dashboard)',
|
|
581
|
+
})
|
|
582
|
+
.option('debug-outcome', {
|
|
583
|
+
type: 'string',
|
|
584
|
+
hidden: true,
|
|
585
|
+
choices: [
|
|
586
|
+
'verified',
|
|
587
|
+
'partial',
|
|
588
|
+
'blocked',
|
|
589
|
+
'failed',
|
|
590
|
+
'incomplete',
|
|
591
|
+
],
|
|
592
|
+
});
|
|
593
|
+
}, async (argv) => {
|
|
594
|
+
await runGoCommand(argv, true);
|
|
595
|
+
})
|
|
596
|
+
.command('add env <env-name>', false, (yargs) => {
|
|
597
|
+
return yargs
|
|
598
|
+
.positional('env-name', {
|
|
599
|
+
type: 'string',
|
|
600
|
+
description: 'Name of the environment (e.g., local, staging, prod)',
|
|
601
|
+
demandOption: true,
|
|
602
|
+
})
|
|
603
|
+
.option('ci', {
|
|
604
|
+
type: 'boolean',
|
|
605
|
+
description: 'Create CI environment (encrypted auth, committed to git)',
|
|
606
|
+
default: false,
|
|
607
|
+
})
|
|
608
|
+
.option('skip-auth', {
|
|
609
|
+
type: 'boolean',
|
|
610
|
+
description: 'Skip browser authentication (just save URL and settings)',
|
|
611
|
+
default: false,
|
|
612
|
+
});
|
|
613
|
+
}, async (argv) => {
|
|
614
|
+
warnRenamed('ranger add env', 'ranger profile add');
|
|
615
|
+
await withTelemetry('add env', (telemetry) => addEnv(argv['env-name'], {
|
|
616
|
+
ci: argv.ci,
|
|
617
|
+
skipAuth: argv['skip-auth'],
|
|
618
|
+
}, telemetry));
|
|
88
619
|
})
|
|
89
|
-
.command('
|
|
90
|
-
|
|
620
|
+
.command('use <env-name>', false, (yargs) => {
|
|
621
|
+
return yargs.positional('env-name', {
|
|
622
|
+
type: 'string',
|
|
623
|
+
description: 'Name of the environment',
|
|
624
|
+
demandOption: true,
|
|
625
|
+
});
|
|
626
|
+
}, async (argv) => {
|
|
627
|
+
warnRenamed('ranger use', 'ranger profile use');
|
|
628
|
+
await withTelemetry('use', () => useEnv(argv['env-name']));
|
|
91
629
|
})
|
|
92
|
-
.command('config',
|
|
630
|
+
.command('config', false, (yargs) => {
|
|
93
631
|
return yargs
|
|
94
|
-
.command('set <env> <key> <value>',
|
|
632
|
+
.command('set <env> <key> <value>', false, (yargs) => {
|
|
95
633
|
return yargs
|
|
96
634
|
.positional('env', {
|
|
97
635
|
type: 'string',
|
|
@@ -109,9 +647,10 @@ yargs(process.argv.slice(2))
|
|
|
109
647
|
demandOption: true,
|
|
110
648
|
});
|
|
111
649
|
}, async (argv) => {
|
|
112
|
-
|
|
650
|
+
warnRenamed('ranger config set', 'ranger profile config set');
|
|
651
|
+
await withTelemetry('config set', () => configSet(argv.env, argv.key, argv.value));
|
|
113
652
|
})
|
|
114
|
-
.command('get <env> <key>',
|
|
653
|
+
.command('get <env> <key>', false, (yargs) => {
|
|
115
654
|
return yargs
|
|
116
655
|
.positional('env', {
|
|
117
656
|
type: 'string',
|
|
@@ -124,18 +663,20 @@ yargs(process.argv.slice(2))
|
|
|
124
663
|
demandOption: true,
|
|
125
664
|
});
|
|
126
665
|
}, async (argv) => {
|
|
127
|
-
|
|
666
|
+
warnRenamed('ranger config get', 'ranger profile config get');
|
|
667
|
+
await withTelemetry('config get', () => configGet(argv.env, argv.key));
|
|
128
668
|
})
|
|
129
|
-
.command('list <env>',
|
|
669
|
+
.command('list <env>', false, (yargs) => {
|
|
130
670
|
return yargs.positional('env', {
|
|
131
671
|
type: 'string',
|
|
132
672
|
description: 'Environment name',
|
|
133
673
|
demandOption: true,
|
|
134
674
|
});
|
|
135
675
|
}, async (argv) => {
|
|
136
|
-
|
|
676
|
+
warnRenamed('ranger config list', 'ranger profile config list');
|
|
677
|
+
await withTelemetry('config list', () => configList(argv.env));
|
|
137
678
|
})
|
|
138
|
-
.command('unset <env> <key>',
|
|
679
|
+
.command('unset <env> <key>', false, (yargs) => {
|
|
139
680
|
return yargs
|
|
140
681
|
.positional('env', {
|
|
141
682
|
type: 'string',
|
|
@@ -148,368 +689,195 @@ yargs(process.argv.slice(2))
|
|
|
148
689
|
demandOption: true,
|
|
149
690
|
});
|
|
150
691
|
}, async (argv) => {
|
|
151
|
-
|
|
692
|
+
warnRenamed('ranger config unset', 'ranger profile config unset');
|
|
693
|
+
await withTelemetry('config unset', () => configUnset(argv.env, argv.key));
|
|
152
694
|
})
|
|
153
|
-
.demandCommand(1, 'You must specify a config subcommand')
|
|
154
|
-
.epilogue(`Supported keys:
|
|
155
|
-
userAgent Browser user agent string
|
|
156
|
-
headless Run browser in headless mode (true/false)
|
|
157
|
-
storageState Path to auth state file (e.g., ./auth.json)
|
|
158
|
-
header.<name> Custom HTTP header (e.g., header.X-Test-Mode)
|
|
159
|
-
|
|
160
|
-
Examples:
|
|
161
|
-
ranger config set ci userAgent "Mozilla/5.0 (CI Bot)"
|
|
162
|
-
ranger config set ci headless true
|
|
163
|
-
ranger config set ci header.Authorization '\${AUTH_TOKEN}'`);
|
|
164
|
-
})
|
|
165
|
-
.command('auth', 'Manage authentication', (yargs) => {
|
|
166
|
-
return yargs
|
|
167
|
-
.command('encrypt <env>', 'Encrypt auth.json for an environment (allows committing to git)', (yargs) => {
|
|
168
|
-
return yargs.positional('env', {
|
|
169
|
-
type: 'string',
|
|
170
|
-
description: 'Environment name',
|
|
171
|
-
demandOption: true,
|
|
172
|
-
});
|
|
173
|
-
}, async (argv) => {
|
|
174
|
-
await authEncrypt(argv.env);
|
|
175
|
-
})
|
|
176
|
-
.demandCommand(1, 'You must specify an auth subcommand')
|
|
177
|
-
.epilogue(`Commands:
|
|
178
|
-
encrypt <env> Encrypt auth.json for safe git storage
|
|
179
|
-
|
|
180
|
-
Examples:
|
|
181
|
-
ranger auth encrypt local # Encrypts .ranger/local/auth.json -> auth.json.enc
|
|
182
|
-
ranger auth encrypt ci # Encrypts .ranger/ci/auth.json -> auth.json.enc`);
|
|
695
|
+
.demandCommand(1, 'You must specify a config subcommand');
|
|
183
696
|
})
|
|
184
|
-
.command('env',
|
|
697
|
+
.command('env', false, (yargs) => {
|
|
185
698
|
return yargs
|
|
186
|
-
.command('ls',
|
|
187
|
-
|
|
699
|
+
.command('ls', false, () => { }, async () => {
|
|
700
|
+
warnRenamed('ranger env ls', 'ranger profile ls');
|
|
701
|
+
await withTelemetry('env list', () => envList());
|
|
188
702
|
})
|
|
189
|
-
.command('update <env-name>',
|
|
703
|
+
.command('update <env-name>', false, (yargs) => {
|
|
190
704
|
return yargs.positional('env-name', {
|
|
191
705
|
type: 'string',
|
|
192
706
|
description: 'Name of the environment to update',
|
|
193
707
|
demandOption: true,
|
|
194
708
|
});
|
|
195
709
|
}, async (argv) => {
|
|
196
|
-
|
|
710
|
+
warnRenamed('ranger env update', 'ranger profile update');
|
|
711
|
+
await withTelemetry('env update', (telemetry) => updateEnv(argv['env-name'], telemetry));
|
|
197
712
|
})
|
|
198
|
-
.demandCommand(1, 'You must specify an env subcommand
|
|
199
|
-
.epilogue(`Commands:
|
|
200
|
-
ls List all environments
|
|
201
|
-
update <env> Update authentication for an environment
|
|
202
|
-
|
|
203
|
-
Examples:
|
|
204
|
-
ranger env ls # List all environments
|
|
205
|
-
ranger env update local # Update auth for local environment`);
|
|
206
|
-
})
|
|
207
|
-
.command('data-mcp-server', 'Run MCP proxy server (reads local credentials)', async () => {
|
|
208
|
-
await dataMcpServer();
|
|
713
|
+
.demandCommand(1, 'You must specify an env subcommand');
|
|
209
714
|
})
|
|
210
|
-
.command('
|
|
715
|
+
.command('feature', false, (yargs) => {
|
|
211
716
|
return yargs
|
|
212
|
-
.
|
|
213
|
-
type: 'string',
|
|
214
|
-
description: 'Description of UI flow to verify',
|
|
215
|
-
demandOption: true,
|
|
216
|
-
})
|
|
217
|
-
.option('env', {
|
|
218
|
-
type: 'string',
|
|
219
|
-
description: 'Environment to use (defaults to active environment)',
|
|
220
|
-
})
|
|
221
|
-
.option('headed', {
|
|
222
|
-
type: 'boolean',
|
|
223
|
-
description: 'Run browser in headed mode (default: headless)',
|
|
224
|
-
default: false,
|
|
225
|
-
});
|
|
226
|
-
}, async (argv) => {
|
|
227
|
-
const result = await verifyInBrowser(argv.task, {
|
|
228
|
-
env: argv.env,
|
|
229
|
-
headed: argv.headed,
|
|
230
|
-
});
|
|
231
|
-
console.log('\n' + '='.repeat(60));
|
|
232
|
-
console.log(result.success ? ' VERIFIED' : ' ISSUES FOUND');
|
|
233
|
-
console.log('='.repeat(60));
|
|
234
|
-
console.log(result.summary);
|
|
235
|
-
if (result.issues?.length) {
|
|
236
|
-
console.log('\nIssues:');
|
|
237
|
-
result.issues.forEach((issue, i) => {
|
|
238
|
-
console.log(`\n${i + 1}. [${issue.severity}] ${issue.description}`);
|
|
239
|
-
if (issue.screenshot)
|
|
240
|
-
console.log(` Screenshot: ${issue.screenshot}`);
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
if (result.traceViewerUrl) {
|
|
244
|
-
console.log(`\nFull Trace: ${result.traceViewerUrl}`);
|
|
245
|
-
}
|
|
246
|
-
// Generate and output markdown report with delimiters
|
|
247
|
-
if (result.sessionId && result.sessionDir) {
|
|
248
|
-
const { screenshots } = await collectScreenshots(result.sessionDir);
|
|
249
|
-
const markdownReport = generateMarkdownReport({
|
|
250
|
-
sessionId: result.sessionId,
|
|
251
|
-
task: result.task || argv.task,
|
|
252
|
-
url: result.url || '',
|
|
253
|
-
success: result.success,
|
|
254
|
-
summary: result.summary,
|
|
255
|
-
issues: result.issues?.filter((issue) => issue.severity !== 'MINOR'),
|
|
256
|
-
screenshots,
|
|
257
|
-
durationMs: result.durationMs || 0,
|
|
258
|
-
traceViewerUrl: result.traceViewerUrl,
|
|
259
|
-
});
|
|
260
|
-
console.log('\n' + '='.repeat(60));
|
|
261
|
-
console.log('VERIFICATION_REPORT_START');
|
|
262
|
-
console.log('='.repeat(60));
|
|
263
|
-
console.log(markdownReport);
|
|
264
|
-
console.log('='.repeat(60));
|
|
265
|
-
console.log('VERIFICATION_REPORT_END');
|
|
266
|
-
console.log('='.repeat(60));
|
|
267
|
-
}
|
|
268
|
-
process.exit(result.success ? 0 : 1);
|
|
269
|
-
})
|
|
270
|
-
.command('status', 'Show Ranger status (version, skills, environments)', () => { }, async () => {
|
|
271
|
-
await status();
|
|
272
|
-
})
|
|
273
|
-
.command('update', 'Update Ranger CLI to the latest version', () => { }, async () => {
|
|
274
|
-
await update();
|
|
275
|
-
})
|
|
276
|
-
.command('feature', 'Manage feature tracking', (yargs) => {
|
|
277
|
-
return yargs
|
|
278
|
-
.command('create <name>', 'Create a new feature with checklist', (yargs) => {
|
|
717
|
+
.command('create <name>', false, (yargs) => {
|
|
279
718
|
return yargs
|
|
280
719
|
.positional('name', {
|
|
281
720
|
type: 'string',
|
|
282
|
-
description: 'Feature name',
|
|
721
|
+
description: 'Feature review name',
|
|
283
722
|
demandOption: true,
|
|
284
723
|
})
|
|
285
724
|
.option('description', {
|
|
286
725
|
type: 'string',
|
|
287
726
|
alias: 'd',
|
|
288
|
-
description: 'Feature description',
|
|
727
|
+
description: 'Feature review description',
|
|
289
728
|
})
|
|
290
729
|
.option('checklist', {
|
|
291
730
|
type: 'array',
|
|
292
731
|
alias: 'c',
|
|
293
|
-
description: '
|
|
732
|
+
description: 'Scenarios (use multiple -c flags for multiple scenarios)',
|
|
733
|
+
})
|
|
734
|
+
.option('scenario', {
|
|
735
|
+
type: 'array',
|
|
736
|
+
hidden: true,
|
|
294
737
|
});
|
|
295
738
|
}, async (argv) => {
|
|
296
|
-
|
|
739
|
+
warnRenamed('ranger feature create', 'ranger create');
|
|
740
|
+
warnFlagRenamed('ranger create', '--checklist', '--scenario');
|
|
741
|
+
const scenarios = argv.scenario ||
|
|
742
|
+
argv.checklist;
|
|
743
|
+
await withTelemetry('feature create', () => featureCreate(argv.name, {
|
|
297
744
|
description: argv.description,
|
|
298
|
-
|
|
299
|
-
});
|
|
745
|
+
scenarios,
|
|
746
|
+
}));
|
|
300
747
|
})
|
|
301
|
-
.command('list',
|
|
748
|
+
.command('list', false, (yargs) => {
|
|
302
749
|
return yargs
|
|
303
750
|
.option('current-branch', {
|
|
304
751
|
type: 'boolean',
|
|
305
|
-
description: 'Filter to
|
|
752
|
+
description: 'Filter to feature reviews for current git branch',
|
|
306
753
|
})
|
|
307
754
|
.option('limit', {
|
|
308
755
|
type: 'number',
|
|
309
756
|
alias: 'l',
|
|
310
|
-
description: 'Maximum number of
|
|
757
|
+
description: 'Maximum number of feature reviews to return',
|
|
311
758
|
default: 10,
|
|
312
759
|
})
|
|
313
760
|
.option('offset', {
|
|
314
761
|
type: 'number',
|
|
315
762
|
alias: 'o',
|
|
316
|
-
description: 'Number of
|
|
763
|
+
description: 'Number of feature reviews to skip',
|
|
317
764
|
default: 0,
|
|
318
765
|
})
|
|
319
766
|
.option('include-deleted', {
|
|
320
767
|
type: 'boolean',
|
|
321
768
|
alias: 'd',
|
|
322
|
-
description: 'Include soft-deleted
|
|
769
|
+
description: 'Include soft-deleted feature reviews',
|
|
323
770
|
default: false,
|
|
324
771
|
});
|
|
325
772
|
}, async (argv) => {
|
|
326
|
-
|
|
773
|
+
warnRenamed('ranger feature list', 'ranger list');
|
|
774
|
+
await withTelemetry('feature list', () => featureList({
|
|
327
775
|
currentBranch: argv['current-branch'],
|
|
328
776
|
limit: argv.limit,
|
|
329
777
|
offset: argv.offset,
|
|
330
778
|
includeDeleted: argv['include-deleted'],
|
|
331
|
-
});
|
|
779
|
+
}));
|
|
332
780
|
})
|
|
333
|
-
.command('show [id]',
|
|
781
|
+
.command('show [id]', false, (yargs) => {
|
|
334
782
|
return yargs.positional('id', {
|
|
335
783
|
type: 'string',
|
|
336
|
-
description: 'Feature ID',
|
|
784
|
+
description: 'Feature review ID',
|
|
337
785
|
});
|
|
338
786
|
}, async (argv) => {
|
|
339
|
-
|
|
787
|
+
warnRenamed('ranger feature show', 'ranger show');
|
|
788
|
+
await withTelemetry('feature show', () => featureShow(argv.id));
|
|
340
789
|
})
|
|
341
|
-
.command('resume [id]',
|
|
790
|
+
.command('resume [id]', false, (yargs) => {
|
|
342
791
|
return yargs.positional('id', {
|
|
343
792
|
type: 'string',
|
|
344
|
-
description: 'Feature ID (optional - bypasses search/prompt)',
|
|
793
|
+
description: 'Feature review ID (optional - bypasses search/prompt)',
|
|
345
794
|
});
|
|
346
795
|
}, async (argv) => {
|
|
347
|
-
|
|
796
|
+
warnRenamed('ranger feature resume', 'ranger resume');
|
|
797
|
+
await withTelemetry('feature resume', () => featureResume(argv.id));
|
|
348
798
|
})
|
|
349
|
-
.command('
|
|
350
|
-
return yargs.positional('id', {
|
|
351
|
-
type: 'string',
|
|
352
|
-
description: 'Feature ID (uses active feature if not provided)',
|
|
353
|
-
});
|
|
354
|
-
}, async (argv) => {
|
|
355
|
-
await featureSessions(argv.id);
|
|
356
|
-
})
|
|
357
|
-
.command('conclude-session [id]', 'End the current session (even with incomplete items)', (yargs) => {
|
|
358
|
-
return yargs.positional('id', {
|
|
359
|
-
type: 'string',
|
|
360
|
-
description: 'Feature ID (uses active feature if not provided)',
|
|
361
|
-
});
|
|
362
|
-
}, async (argv) => {
|
|
363
|
-
await featureConcludeSession(argv.id);
|
|
364
|
-
})
|
|
365
|
-
.command('add-checklist-item <description>', 'Add a checklist item to the active feature', (yargs) => {
|
|
799
|
+
.command('add-checklist-item <description>', false, (yargs) => {
|
|
366
800
|
return yargs
|
|
367
801
|
.positional('description', {
|
|
368
802
|
type: 'string',
|
|
369
|
-
description: '
|
|
803
|
+
description: 'Scenario description',
|
|
370
804
|
demandOption: true,
|
|
371
805
|
})
|
|
372
806
|
.option('id', {
|
|
373
807
|
type: 'string',
|
|
374
|
-
description: 'Feature ID (uses active feature if not provided)',
|
|
808
|
+
description: 'Feature review ID (uses active feature review if not provided)',
|
|
375
809
|
});
|
|
376
810
|
}, async (argv) => {
|
|
377
|
-
|
|
811
|
+
warnRenamed('ranger feature add-checklist-item', 'ranger add-scenario');
|
|
812
|
+
await withTelemetry('feature add-checklist-item', () => featureAddScenario(argv.description, argv.id));
|
|
378
813
|
})
|
|
379
|
-
.command('get-feedback [id]',
|
|
814
|
+
.command('get-feedback [id]', false, (yargs) => {
|
|
380
815
|
return yargs.positional('id', {
|
|
381
816
|
type: 'string',
|
|
382
|
-
description: 'Feature ID (uses active feature if not provided)',
|
|
817
|
+
description: 'Feature review ID (uses active feature review if not provided)',
|
|
383
818
|
});
|
|
384
819
|
}, async (argv) => {
|
|
385
|
-
|
|
820
|
+
warnRenamed('ranger feature get-feedback', 'ranger get-review');
|
|
821
|
+
await withTelemetry('feature get-feedback', () => featureGetReview(argv.id));
|
|
386
822
|
})
|
|
387
|
-
.command('delete [id]',
|
|
823
|
+
.command('delete [id]', false, (yargs) => {
|
|
388
824
|
return yargs.positional('id', {
|
|
389
825
|
type: 'string',
|
|
390
|
-
description: 'Feature ID',
|
|
826
|
+
description: 'Feature review ID',
|
|
391
827
|
});
|
|
392
828
|
}, async (argv) => {
|
|
393
|
-
|
|
829
|
+
warnRenamed('ranger feature delete', 'ranger delete');
|
|
830
|
+
await withTelemetry('feature delete', () => featureDelete(argv.id));
|
|
394
831
|
})
|
|
395
|
-
.command('restore <id>',
|
|
832
|
+
.command('restore <id>', false, (yargs) => {
|
|
396
833
|
return yargs.positional('id', {
|
|
397
834
|
type: 'string',
|
|
398
|
-
description: 'Feature ID to restore',
|
|
835
|
+
description: 'Feature review ID to restore',
|
|
399
836
|
demandOption: true,
|
|
400
837
|
});
|
|
401
838
|
}, async (argv) => {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
.demandCommand(1, 'You must specify a feature subcommand')
|
|
405
|
-
.epilogue(`Commands:
|
|
406
|
-
create <name> Create a new feature with checklist
|
|
407
|
-
list List all features
|
|
408
|
-
show [id] Show feature details
|
|
409
|
-
resume [id] Resume a feature by ID
|
|
410
|
-
sessions [id] List sessions for a feature
|
|
411
|
-
conclude-session [id] End the current session (even with incomplete items)
|
|
412
|
-
add-checklist-item Add a checklist item to the active feature
|
|
413
|
-
get-feedback [id] Show reviewer feedback for action items
|
|
414
|
-
delete [id] Soft delete a feature
|
|
415
|
-
restore <id> Restore a soft-deleted feature
|
|
416
|
-
|
|
417
|
-
Examples:
|
|
418
|
-
ranger feature create "User auth" -c "Login works" -c "Signup works"
|
|
419
|
-
ranger feature list --current-branch
|
|
420
|
-
ranger feature list --include-deleted
|
|
421
|
-
ranger feature show
|
|
422
|
-
ranger feature resume feat_abc123
|
|
423
|
-
ranger feature add-checklist-item "User can reset password"
|
|
424
|
-
ranger feature get-feedback
|
|
425
|
-
ranger feature delete
|
|
426
|
-
ranger feature restore feat_abc123`);
|
|
427
|
-
})
|
|
428
|
-
.command('verify-feature', 'Verify a checklist item in the browser (requires active feature)', (yargs) => {
|
|
429
|
-
return yargs
|
|
430
|
-
.option('env', {
|
|
431
|
-
type: 'string',
|
|
432
|
-
description: 'Environment to use (defaults to active environment)',
|
|
433
|
-
})
|
|
434
|
-
.option('task', {
|
|
435
|
-
type: 'string',
|
|
436
|
-
description: 'Task description (defaults to checklist item description)',
|
|
839
|
+
warnRenamed('ranger feature restore', 'ranger restore');
|
|
840
|
+
await withTelemetry('feature restore', () => featureRestore(argv.id));
|
|
437
841
|
})
|
|
438
|
-
.
|
|
439
|
-
type: 'number',
|
|
440
|
-
description: 'Checklist item index (1-based)',
|
|
441
|
-
})
|
|
442
|
-
.option('start-path', {
|
|
443
|
-
type: 'string',
|
|
444
|
-
description: 'Path to start on (appended to base URL, e.g., /dashboard)',
|
|
445
|
-
})
|
|
446
|
-
.option('debug-outcome', {
|
|
447
|
-
type: 'string',
|
|
448
|
-
hidden: true,
|
|
449
|
-
choices: [
|
|
450
|
-
'verified',
|
|
451
|
-
'partial',
|
|
452
|
-
'blocked',
|
|
453
|
-
'failed',
|
|
454
|
-
'incomplete',
|
|
455
|
-
],
|
|
456
|
-
});
|
|
457
|
-
}, async (argv) => {
|
|
458
|
-
const result = await verifyFeature({
|
|
459
|
-
env: argv.env,
|
|
460
|
-
task: argv.task,
|
|
461
|
-
item: argv.item,
|
|
462
|
-
startPath: argv['start-path'],
|
|
463
|
-
debugOutcome: argv['debug-outcome'],
|
|
464
|
-
});
|
|
465
|
-
console.log('\n' + '='.repeat(60));
|
|
466
|
-
console.log(result.evaluation === 'verified'
|
|
467
|
-
? ' VERIFIED'
|
|
468
|
-
: result.evaluation === 'partial'
|
|
469
|
-
? ' PARTIAL'
|
|
470
|
-
: result.evaluation === 'blocked'
|
|
471
|
-
? ' BLOCKED'
|
|
472
|
-
: ' FAILED');
|
|
473
|
-
console.log('='.repeat(60));
|
|
474
|
-
console.log(`Summary: ${result.summary}`);
|
|
475
|
-
console.log(`Evaluation: ${result.evaluation}`);
|
|
476
|
-
console.log(`Reason: ${result.evaluationReason}`);
|
|
477
|
-
if (result.issues?.length) {
|
|
478
|
-
console.log('\nIssues:');
|
|
479
|
-
result.issues.forEach((issue, i) => {
|
|
480
|
-
console.log(`\n${i + 1}. [${issue.severity}] ${issue.description}`);
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
process.exit(result.evaluation === 'verified' ? 0 : 1);
|
|
842
|
+
.demandCommand(1, 'You must specify a feature review subcommand');
|
|
484
843
|
})
|
|
485
|
-
// Unified hook command (hidden from help - invoked automatically by Claude Code
|
|
844
|
+
// Unified hook command (hidden from help - invoked automatically by Claude Code / OpenCode plugins)
|
|
486
845
|
.command('hook', false, (yargs) => {
|
|
487
846
|
return yargs
|
|
488
|
-
.command('enable', 'Enable Ranger hooks for this session', () => {
|
|
489
|
-
|
|
847
|
+
.command('enable', 'Enable Ranger hooks for this session', (yargs) => {
|
|
848
|
+
return yargs.option('session-id', {
|
|
849
|
+
type: 'string',
|
|
850
|
+
description: 'Session ID (for non-Claude integrations like OpenCode)',
|
|
851
|
+
});
|
|
852
|
+
}, async (argv) => {
|
|
853
|
+
await hook('enable', argv['session-id']);
|
|
490
854
|
})
|
|
491
|
-
.command('disable', 'Disable Ranger hooks for this session', () => {
|
|
492
|
-
|
|
855
|
+
.command('disable', 'Disable Ranger hooks for this session', (yargs) => {
|
|
856
|
+
return yargs.option('session-id', {
|
|
857
|
+
type: 'string',
|
|
858
|
+
description: 'Session ID (for non-Claude integrations like OpenCode)',
|
|
859
|
+
});
|
|
860
|
+
}, async (argv) => {
|
|
861
|
+
await hook('disable', argv['session-id']);
|
|
493
862
|
})
|
|
494
863
|
.option('name', {
|
|
495
864
|
type: 'string',
|
|
496
865
|
description: 'Hook name (session-start, pre-compact, post-edit, etc.)',
|
|
866
|
+
})
|
|
867
|
+
.option('session-id', {
|
|
868
|
+
type: 'string',
|
|
869
|
+
description: 'Session ID (for non-Claude integrations like OpenCode)',
|
|
497
870
|
});
|
|
498
871
|
}, async (argv) => {
|
|
499
872
|
if (argv.name) {
|
|
500
|
-
await hook(argv.name);
|
|
873
|
+
await hook(argv.name, argv['session-id']);
|
|
501
874
|
}
|
|
502
875
|
})
|
|
503
876
|
.demandCommand(1, 'You must specify a command')
|
|
504
877
|
.strictCommands()
|
|
505
|
-
.fail((msg, err, yargs) => {
|
|
878
|
+
.fail(async (msg, err, yargs) => {
|
|
506
879
|
const rawCommand = sanitizeArgs(process.argv.slice(2));
|
|
507
880
|
const errorType = getErrorType(msg, err);
|
|
508
|
-
logDesirePath({
|
|
509
|
-
rawCommand,
|
|
510
|
-
errorMessage: msg || err?.message,
|
|
511
|
-
errorType,
|
|
512
|
-
});
|
|
513
881
|
if (msg && msg.includes('Unknown command')) {
|
|
514
882
|
const command = process.argv[2];
|
|
515
883
|
console.error(`\nUnknown command: ${command}`);
|
|
@@ -521,9 +889,21 @@ Examples:
|
|
|
521
889
|
else if (err) {
|
|
522
890
|
console.error(`\nError: ${err.message}\n`);
|
|
523
891
|
}
|
|
524
|
-
|
|
525
|
-
|
|
892
|
+
await logDesirePath({
|
|
893
|
+
rawCommand,
|
|
894
|
+
errorMessage: msg || err?.message,
|
|
895
|
+
errorType,
|
|
896
|
+
});
|
|
897
|
+
// Report to telemetry if there's an active collector
|
|
898
|
+
const collector = getCurrentCollector();
|
|
899
|
+
if (collector) {
|
|
900
|
+
await collector.trackCommandError(err || new Error(msg || 'Unknown error'));
|
|
901
|
+
}
|
|
902
|
+
process.exit(1);
|
|
526
903
|
})
|
|
904
|
+
.epilogue('Documentation:\n' +
|
|
905
|
+
' https://docs.ranger.net\n' +
|
|
906
|
+
' https://docs.ranger.net/llms.txt (for LLMs)')
|
|
527
907
|
.help()
|
|
528
908
|
.alias('help', 'h')
|
|
529
909
|
.parse();
|