@ranger-testing/ranger-cli 1.1.7 → 2.0.1
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 +649 -277
- 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 +174 -123
- package/build/commands/feature.js.map +1 -1
- package/build/commands/hooks/autoPrompt.js +1 -1
- package/build/commands/hooks/disable.js +1 -1
- package/build/commands/hooks/enable.js +9 -4
- package/build/commands/hooks/enable.js.map +1 -1
- package/build/commands/hooks/exitPlanMode.js +8 -8
- package/build/commands/hooks/planReminder.js +7 -7
- package/build/commands/hooks/planStart.js +4 -4
- package/build/commands/hooks/postEdit.js +4 -4
- package/build/commands/hooks/postEdit.js.map +1 -1
- package/build/commands/hooks/preCompact.js +3 -3
- package/build/commands/hooks/preCompact.js.map +1 -1
- package/build/commands/hooks/sessionStart.js +19 -5
- package/build/commands/hooks/sessionStart.js.map +1 -1
- package/build/commands/hooks/stopHook.js +114 -20
- 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 +52 -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/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 +55 -30
- 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/settings.js +20 -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 +816 -526
- 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,283 @@ 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
|
+
debugAddressComments: argv['debug-address-comments'],
|
|
130
|
+
});
|
|
131
|
+
console.log('\n' + '='.repeat(60));
|
|
132
|
+
console.log(result.evaluation === 'verified'
|
|
133
|
+
? ' VERIFIED'
|
|
134
|
+
: result.evaluation === 'incomplete'
|
|
135
|
+
? ' INCOMPLETE'
|
|
136
|
+
: result.evaluation === 'partial'
|
|
137
|
+
? ' PARTIAL'
|
|
138
|
+
: result.evaluation === 'blocked'
|
|
139
|
+
? ' BLOCKED'
|
|
140
|
+
: ' FAILED');
|
|
141
|
+
console.log('='.repeat(60));
|
|
142
|
+
console.log(`Summary: ${result.summary}`);
|
|
143
|
+
console.log(`Evaluation: ${result.evaluation}`);
|
|
144
|
+
console.log(`Reason: ${result.evaluationReason}`);
|
|
145
|
+
if (result.issues?.length) {
|
|
146
|
+
console.log('\nIssues:');
|
|
147
|
+
result.issues.forEach((issue, i) => {
|
|
148
|
+
console.log(`\n${i + 1}. [${issue.severity}] ${issue.description}`);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
process.exit(result.evaluation === 'verified' ? 0 : 1);
|
|
152
|
+
}
|
|
32
153
|
// Setup yargs CLI
|
|
33
154
|
yargs(process.argv.slice(2))
|
|
155
|
+
.scriptName('ranger')
|
|
156
|
+
.usage(TOP_LEVEL_HELP)
|
|
34
157
|
.version(getCurrentVersion())
|
|
35
|
-
.command('
|
|
158
|
+
.command('create <name>', 'Create a new feature review with scenarios', (yargs) => {
|
|
36
159
|
return yargs
|
|
37
|
-
.positional('
|
|
160
|
+
.positional('name', {
|
|
38
161
|
type: 'string',
|
|
39
|
-
description: '
|
|
162
|
+
description: 'Feature review name',
|
|
40
163
|
demandOption: true,
|
|
41
164
|
})
|
|
42
|
-
.option('
|
|
165
|
+
.option('description', {
|
|
166
|
+
type: 'string',
|
|
167
|
+
alias: 'd',
|
|
168
|
+
description: 'Feature review description',
|
|
169
|
+
})
|
|
170
|
+
.option('scenario', {
|
|
171
|
+
type: 'array',
|
|
172
|
+
alias: 'c',
|
|
173
|
+
description: 'Scenarios (use multiple -c flags for multiple scenarios)',
|
|
174
|
+
})
|
|
175
|
+
.option('checklist', {
|
|
176
|
+
type: 'array',
|
|
177
|
+
hidden: true,
|
|
178
|
+
});
|
|
179
|
+
}, async (argv) => {
|
|
180
|
+
warnFlagRenamed('ranger create', '--checklist', '--scenario');
|
|
181
|
+
const scenarios = argv.scenario ||
|
|
182
|
+
argv.checklist;
|
|
183
|
+
await withTelemetry('create', () => featureCreate(argv.name, {
|
|
184
|
+
description: argv.description,
|
|
185
|
+
scenarios,
|
|
186
|
+
}));
|
|
187
|
+
})
|
|
188
|
+
.command('list', 'List all feature reviews', (yargs) => {
|
|
189
|
+
return yargs
|
|
190
|
+
.option('current-branch', {
|
|
43
191
|
type: 'boolean',
|
|
44
|
-
description: '
|
|
45
|
-
default: false,
|
|
192
|
+
description: 'Filter to feature reviews for current git branch',
|
|
46
193
|
})
|
|
47
|
-
.option('
|
|
194
|
+
.option('limit', {
|
|
195
|
+
type: 'number',
|
|
196
|
+
alias: 'l',
|
|
197
|
+
description: 'Maximum number of feature reviews to return',
|
|
198
|
+
default: 10,
|
|
199
|
+
})
|
|
200
|
+
.option('offset', {
|
|
201
|
+
type: 'number',
|
|
202
|
+
alias: 'o',
|
|
203
|
+
description: 'Number of feature reviews to skip',
|
|
204
|
+
default: 0,
|
|
205
|
+
})
|
|
206
|
+
.option('include-deleted', {
|
|
48
207
|
type: 'boolean',
|
|
49
|
-
|
|
208
|
+
alias: 'd',
|
|
209
|
+
description: 'Include soft-deleted feature reviews',
|
|
50
210
|
default: false,
|
|
51
211
|
});
|
|
52
212
|
}, async (argv) => {
|
|
53
|
-
await
|
|
54
|
-
|
|
55
|
-
|
|
213
|
+
await withTelemetry('list', () => featureList({
|
|
214
|
+
currentBranch: argv['current-branch'],
|
|
215
|
+
limit: argv.limit,
|
|
216
|
+
offset: argv.offset,
|
|
217
|
+
includeDeleted: argv['include-deleted'],
|
|
218
|
+
}));
|
|
219
|
+
})
|
|
220
|
+
.command('show [id]', 'Show feature review details (uses active feature review if no id)', (yargs) => {
|
|
221
|
+
return yargs.positional('id', {
|
|
222
|
+
type: 'string',
|
|
223
|
+
description: 'Feature review ID',
|
|
56
224
|
});
|
|
225
|
+
}, async (argv) => {
|
|
226
|
+
await withTelemetry('show', () => featureShow(argv.id));
|
|
57
227
|
})
|
|
58
|
-
.command('
|
|
59
|
-
return yargs.positional('
|
|
228
|
+
.command('resume [id]', 'Find and use feature review matching current git context', (yargs) => {
|
|
229
|
+
return yargs.positional('id', {
|
|
60
230
|
type: 'string',
|
|
61
|
-
description: '
|
|
231
|
+
description: 'Feature review ID (optional - bypasses search/prompt)',
|
|
232
|
+
});
|
|
233
|
+
}, async (argv) => {
|
|
234
|
+
await withTelemetry('resume', () => featureResume(argv.id));
|
|
235
|
+
})
|
|
236
|
+
.command('add-scenario <description>', 'Add a scenario to the active feature review', (yargs) => {
|
|
237
|
+
return yargs
|
|
238
|
+
.positional('description', {
|
|
239
|
+
type: 'string',
|
|
240
|
+
description: 'Scenario description',
|
|
62
241
|
demandOption: true,
|
|
242
|
+
})
|
|
243
|
+
.option('id', {
|
|
244
|
+
type: 'string',
|
|
245
|
+
description: 'Feature review ID (uses active feature review if not provided)',
|
|
63
246
|
});
|
|
64
247
|
}, async (argv) => {
|
|
65
|
-
await
|
|
248
|
+
await withTelemetry('add-scenario', () => featureAddScenario(argv.description, argv.id));
|
|
66
249
|
})
|
|
67
|
-
.command('
|
|
250
|
+
.command('edit-scenario <description>', 'Edit a scenario description on the active feature review', (yargs) => {
|
|
251
|
+
return yargs
|
|
252
|
+
.positional('description', {
|
|
253
|
+
type: 'string',
|
|
254
|
+
description: 'New scenario description',
|
|
255
|
+
demandOption: true,
|
|
256
|
+
})
|
|
257
|
+
.option('scenario', {
|
|
258
|
+
type: 'number',
|
|
259
|
+
description: 'Scenario number to edit (1-based)',
|
|
260
|
+
demandOption: true,
|
|
261
|
+
})
|
|
262
|
+
.option('id', {
|
|
263
|
+
type: 'string',
|
|
264
|
+
description: 'Feature review ID (uses active feature review if not provided)',
|
|
265
|
+
});
|
|
266
|
+
}, async (argv) => {
|
|
267
|
+
await withTelemetry('edit-scenario', () => featureEditScenario(argv.description, {
|
|
268
|
+
id: argv.id,
|
|
269
|
+
scenario: argv.scenario,
|
|
270
|
+
}));
|
|
271
|
+
})
|
|
272
|
+
.command('get-review [id]', 'Show reviewer feedback (comments) for all scenarios', (yargs) => {
|
|
273
|
+
return yargs.positional('id', {
|
|
274
|
+
type: 'string',
|
|
275
|
+
description: 'Feature review ID (uses active feature review if not provided)',
|
|
276
|
+
});
|
|
277
|
+
}, async (argv) => {
|
|
278
|
+
await withTelemetry('get-review', () => featureGetReview(argv.id));
|
|
279
|
+
})
|
|
280
|
+
.command('delete [id]', 'Soft delete a feature review (uses active feature review if no id)', (yargs) => {
|
|
281
|
+
return yargs.positional('id', {
|
|
282
|
+
type: 'string',
|
|
283
|
+
description: 'Feature review ID',
|
|
284
|
+
});
|
|
285
|
+
}, async (argv) => {
|
|
286
|
+
await withTelemetry('delete', () => featureDelete(argv.id));
|
|
287
|
+
})
|
|
288
|
+
.command('restore <id>', 'Restore a soft-deleted feature review', (yargs) => {
|
|
289
|
+
return yargs.positional('id', {
|
|
290
|
+
type: 'string',
|
|
291
|
+
description: 'Feature review ID to restore',
|
|
292
|
+
demandOption: true,
|
|
293
|
+
});
|
|
294
|
+
}, async (argv) => {
|
|
295
|
+
await withTelemetry('restore', () => featureRestore(argv.id));
|
|
296
|
+
})
|
|
297
|
+
.command('setup [token]', 'Initialize Ranger in your project', (yargs) => {
|
|
68
298
|
return yargs
|
|
69
299
|
.positional('token', {
|
|
70
300
|
type: 'string',
|
|
@@ -76,22 +306,335 @@ yargs(process.argv.slice(2))
|
|
|
76
306
|
default: false,
|
|
77
307
|
});
|
|
78
308
|
}, async (argv) => {
|
|
79
|
-
await start(argv.token, {
|
|
309
|
+
await withTelemetry('start', (telemetry) => start(argv.token, {
|
|
80
310
|
skipChromium: argv['skip-chromium'],
|
|
311
|
+
}, telemetry));
|
|
312
|
+
})
|
|
313
|
+
.command('setup-ci <token>', 'Set up Ranger for CI (non-interactive)', (yargs) => {
|
|
314
|
+
return yargs
|
|
315
|
+
.positional('token', {
|
|
316
|
+
type: 'string',
|
|
317
|
+
description: 'Ranger API token',
|
|
318
|
+
demandOption: true,
|
|
319
|
+
})
|
|
320
|
+
.option('profile', {
|
|
321
|
+
type: 'string',
|
|
322
|
+
description: 'CI profile name (auto-detected if only one exists)',
|
|
323
|
+
})
|
|
324
|
+
.option('base-url', {
|
|
325
|
+
type: 'string',
|
|
326
|
+
description: 'Base URL for the app (creates/updates profile settings)',
|
|
327
|
+
})
|
|
328
|
+
.option('skip-chromium', {
|
|
329
|
+
type: 'boolean',
|
|
330
|
+
description: 'Skip Chromium browser installation',
|
|
331
|
+
default: false,
|
|
81
332
|
});
|
|
333
|
+
}, async (argv) => {
|
|
334
|
+
await withTelemetry('setup-ci', (telemetry) => setupCi(argv.token, {
|
|
335
|
+
profile: argv.profile,
|
|
336
|
+
baseUrl: argv['base-url'],
|
|
337
|
+
skipChromium: argv['skip-chromium'],
|
|
338
|
+
}, telemetry));
|
|
82
339
|
})
|
|
83
340
|
.command('login', 'Log in to Ranger via browser (re-authenticate without full setup)', () => { }, async () => {
|
|
84
|
-
await login();
|
|
341
|
+
await withTelemetry('login', () => login());
|
|
85
342
|
})
|
|
86
343
|
.command('skillup', 'Install Ranger skills for Claude Code', () => { }, async () => {
|
|
87
|
-
await skillup();
|
|
344
|
+
await withTelemetry('skillup', () => skillup());
|
|
88
345
|
})
|
|
89
|
-
.command('clean', 'Remove all Ranger artifacts from the project', async () => {
|
|
90
|
-
await clean();
|
|
346
|
+
.command('clean', 'Remove all Ranger artifacts from the project', () => { }, async () => {
|
|
347
|
+
await withTelemetry('clean', () => clean());
|
|
91
348
|
})
|
|
92
|
-
.command('
|
|
349
|
+
.command('profile', 'Manage profiles', (yargs) => {
|
|
93
350
|
return yargs
|
|
94
|
-
.
|
|
351
|
+
.usage(PROFILE_HELP)
|
|
352
|
+
.command('add <profile-name>', 'Add profile configuration', (yargs) => {
|
|
353
|
+
return yargs
|
|
354
|
+
.positional('profile-name', {
|
|
355
|
+
type: 'string',
|
|
356
|
+
description: 'Name of the profile (e.g., local, staging, prod)',
|
|
357
|
+
demandOption: true,
|
|
358
|
+
})
|
|
359
|
+
.option('ci', {
|
|
360
|
+
type: 'boolean',
|
|
361
|
+
description: 'Create CI profile (encrypted auth, committed to git)',
|
|
362
|
+
default: false,
|
|
363
|
+
})
|
|
364
|
+
.option('skip-auth', {
|
|
365
|
+
type: 'boolean',
|
|
366
|
+
description: 'Skip browser authentication (just save URL and settings)',
|
|
367
|
+
default: false,
|
|
368
|
+
});
|
|
369
|
+
}, async (argv) => {
|
|
370
|
+
await withTelemetry('profile add', (telemetry) => addEnv(argv['profile-name'], {
|
|
371
|
+
ci: argv.ci,
|
|
372
|
+
skipAuth: argv['skip-auth'],
|
|
373
|
+
}, telemetry));
|
|
374
|
+
})
|
|
375
|
+
.command('use <profile-name>', 'Switch to using a specific profile', (yargs) => {
|
|
376
|
+
return yargs.positional('profile-name', {
|
|
377
|
+
type: 'string',
|
|
378
|
+
description: 'Name of the profile',
|
|
379
|
+
demandOption: true,
|
|
380
|
+
});
|
|
381
|
+
}, async (argv) => {
|
|
382
|
+
await withTelemetry('profile use', () => useEnv(argv['profile-name']));
|
|
383
|
+
})
|
|
384
|
+
.command('encrypt-auth <profile>', 'Encrypt auth.json for a profile (allows committing to git)', (yargs) => {
|
|
385
|
+
return yargs.positional('profile', {
|
|
386
|
+
type: 'string',
|
|
387
|
+
description: 'Profile name',
|
|
388
|
+
demandOption: true,
|
|
389
|
+
});
|
|
390
|
+
}, async (argv) => {
|
|
391
|
+
await withTelemetry('profile encrypt-auth', () => authEncrypt(argv.profile));
|
|
392
|
+
})
|
|
393
|
+
.command('ls', 'List all profiles', () => { }, async () => {
|
|
394
|
+
await withTelemetry('profile ls', () => envList());
|
|
395
|
+
})
|
|
396
|
+
.command('update <profile-name>', 'Update authentication for an existing profile', (yargs) => {
|
|
397
|
+
return yargs.positional('profile-name', {
|
|
398
|
+
type: 'string',
|
|
399
|
+
description: 'Name of the profile to update',
|
|
400
|
+
demandOption: true,
|
|
401
|
+
});
|
|
402
|
+
}, async (argv) => {
|
|
403
|
+
await withTelemetry('profile update', (telemetry) => updateEnv(argv['profile-name'], telemetry));
|
|
404
|
+
})
|
|
405
|
+
.command('config', 'Manage profile configuration', (yargs) => {
|
|
406
|
+
return yargs
|
|
407
|
+
.usage(PROFILE_CONFIG_HELP)
|
|
408
|
+
.command('set <profile> <key> <value>', 'Set a config value', (yargs) => {
|
|
409
|
+
return yargs
|
|
410
|
+
.positional('profile', {
|
|
411
|
+
type: 'string',
|
|
412
|
+
description: 'Profile name',
|
|
413
|
+
demandOption: true,
|
|
414
|
+
})
|
|
415
|
+
.positional('key', {
|
|
416
|
+
type: 'string',
|
|
417
|
+
description: 'Config key (e.g., userAgent, header.X-Custom)',
|
|
418
|
+
demandOption: true,
|
|
419
|
+
})
|
|
420
|
+
.positional('value', {
|
|
421
|
+
type: 'string',
|
|
422
|
+
description: 'Config value',
|
|
423
|
+
demandOption: true,
|
|
424
|
+
});
|
|
425
|
+
}, async (argv) => {
|
|
426
|
+
await withTelemetry('profile config set', () => configSet(argv.profile, argv.key, argv.value));
|
|
427
|
+
})
|
|
428
|
+
.command('get <profile> <key>', 'Get a config value', (yargs) => {
|
|
429
|
+
return yargs
|
|
430
|
+
.positional('profile', {
|
|
431
|
+
type: 'string',
|
|
432
|
+
description: 'Profile name',
|
|
433
|
+
demandOption: true,
|
|
434
|
+
})
|
|
435
|
+
.positional('key', {
|
|
436
|
+
type: 'string',
|
|
437
|
+
description: 'Config key',
|
|
438
|
+
demandOption: true,
|
|
439
|
+
});
|
|
440
|
+
}, async (argv) => {
|
|
441
|
+
await withTelemetry('profile config get', () => configGet(argv.profile, argv.key));
|
|
442
|
+
})
|
|
443
|
+
.command('list <profile>', 'List all config for a profile', (yargs) => {
|
|
444
|
+
return yargs.positional('profile', {
|
|
445
|
+
type: 'string',
|
|
446
|
+
description: 'Profile name',
|
|
447
|
+
demandOption: true,
|
|
448
|
+
});
|
|
449
|
+
}, async (argv) => {
|
|
450
|
+
await withTelemetry('profile config list', () => configList(argv.profile));
|
|
451
|
+
})
|
|
452
|
+
.command('unset <profile> <key>', 'Remove a config value', (yargs) => {
|
|
453
|
+
return yargs
|
|
454
|
+
.positional('profile', {
|
|
455
|
+
type: 'string',
|
|
456
|
+
description: 'Profile name',
|
|
457
|
+
demandOption: true,
|
|
458
|
+
})
|
|
459
|
+
.positional('key', {
|
|
460
|
+
type: 'string',
|
|
461
|
+
description: 'Config key to remove',
|
|
462
|
+
demandOption: true,
|
|
463
|
+
});
|
|
464
|
+
}, async (argv) => {
|
|
465
|
+
await withTelemetry('profile config unset', () => configUnset(argv.profile, argv.key));
|
|
466
|
+
})
|
|
467
|
+
.demandCommand(1, 'You must specify a profile config subcommand');
|
|
468
|
+
})
|
|
469
|
+
.demandCommand(1, 'You must specify a profile subcommand');
|
|
470
|
+
})
|
|
471
|
+
.command('status', 'Show Ranger status (version, skills, profiles)', () => { }, async () => {
|
|
472
|
+
await withTelemetry('status', () => status());
|
|
473
|
+
})
|
|
474
|
+
.command('update', 'Update Ranger CLI to the latest version', () => { }, async () => {
|
|
475
|
+
await withTelemetry('update', () => update());
|
|
476
|
+
})
|
|
477
|
+
.command('go', 'Verify a scenario in the browser (requires active feature review)', (yargs) => {
|
|
478
|
+
return yargs
|
|
479
|
+
.option('profile', {
|
|
480
|
+
type: 'string',
|
|
481
|
+
description: 'Profile to use (defaults to active profile)',
|
|
482
|
+
})
|
|
483
|
+
.option('notes', {
|
|
484
|
+
type: 'string',
|
|
485
|
+
description: 'Notes for verification (defaults to scenario description)',
|
|
486
|
+
})
|
|
487
|
+
.option('scenario', {
|
|
488
|
+
type: 'number',
|
|
489
|
+
description: 'Scenario index (1-based)',
|
|
490
|
+
})
|
|
491
|
+
.option('start-path', {
|
|
492
|
+
type: 'string',
|
|
493
|
+
description: 'Path to start on (appended to base URL, e.g., /dashboard)',
|
|
494
|
+
})
|
|
495
|
+
.option('debug-outcome', {
|
|
496
|
+
type: 'string',
|
|
497
|
+
hidden: true,
|
|
498
|
+
choices: [
|
|
499
|
+
'verified',
|
|
500
|
+
'partial',
|
|
501
|
+
'blocked',
|
|
502
|
+
'failed',
|
|
503
|
+
'incomplete',
|
|
504
|
+
],
|
|
505
|
+
})
|
|
506
|
+
.option('debug-address-comments', {
|
|
507
|
+
type: 'boolean',
|
|
508
|
+
hidden: true,
|
|
509
|
+
})
|
|
510
|
+
.option('env', {
|
|
511
|
+
type: 'string',
|
|
512
|
+
hidden: true,
|
|
513
|
+
})
|
|
514
|
+
.option('task', {
|
|
515
|
+
type: 'string',
|
|
516
|
+
hidden: true,
|
|
517
|
+
})
|
|
518
|
+
.option('item', {
|
|
519
|
+
type: 'number',
|
|
520
|
+
hidden: true,
|
|
521
|
+
});
|
|
522
|
+
}, async (argv) => {
|
|
523
|
+
await runGoCommand(argv);
|
|
524
|
+
})
|
|
525
|
+
// Legacy command shims (hidden from help)
|
|
526
|
+
.command('auth', false, (yargs) => {
|
|
527
|
+
return yargs
|
|
528
|
+
.command('encrypt <profile>', false, (yargs) => {
|
|
529
|
+
return yargs.positional('profile', {
|
|
530
|
+
type: 'string',
|
|
531
|
+
description: 'Profile name',
|
|
532
|
+
demandOption: true,
|
|
533
|
+
});
|
|
534
|
+
}, async (argv) => {
|
|
535
|
+
warnRenamed('ranger auth encrypt', 'ranger profile encrypt-auth');
|
|
536
|
+
await authEncrypt(argv.profile);
|
|
537
|
+
})
|
|
538
|
+
.demandCommand(1, 'You must specify an auth subcommand');
|
|
539
|
+
}, () => { })
|
|
540
|
+
.command('start [token]', false, (yargs) => {
|
|
541
|
+
return yargs
|
|
542
|
+
.positional('token', {
|
|
543
|
+
type: 'string',
|
|
544
|
+
description: 'Ranger API token (omit to log in via browser)',
|
|
545
|
+
})
|
|
546
|
+
.option('skip-chromium', {
|
|
547
|
+
type: 'boolean',
|
|
548
|
+
description: 'Skip Chromium browser check and installation',
|
|
549
|
+
default: false,
|
|
550
|
+
});
|
|
551
|
+
}, async (argv) => {
|
|
552
|
+
warnRenamed('ranger start', 'ranger setup');
|
|
553
|
+
await withTelemetry('start', (telemetry) => start(argv.token, {
|
|
554
|
+
skipChromium: argv['skip-chromium'],
|
|
555
|
+
}, telemetry));
|
|
556
|
+
})
|
|
557
|
+
.command('verify-feature', false, (yargs) => {
|
|
558
|
+
return yargs
|
|
559
|
+
.option('env', {
|
|
560
|
+
type: 'string',
|
|
561
|
+
description: 'Profile to use (defaults to active profile)',
|
|
562
|
+
})
|
|
563
|
+
.option('task', {
|
|
564
|
+
type: 'string',
|
|
565
|
+
description: 'Task description (defaults to scenario description)',
|
|
566
|
+
})
|
|
567
|
+
.option('item', {
|
|
568
|
+
type: 'number',
|
|
569
|
+
description: 'Scenario index (1-based)',
|
|
570
|
+
})
|
|
571
|
+
.option('profile', {
|
|
572
|
+
type: 'string',
|
|
573
|
+
hidden: true,
|
|
574
|
+
})
|
|
575
|
+
.option('notes', {
|
|
576
|
+
type: 'string',
|
|
577
|
+
hidden: true,
|
|
578
|
+
})
|
|
579
|
+
.option('scenario', {
|
|
580
|
+
type: 'number',
|
|
581
|
+
hidden: true,
|
|
582
|
+
})
|
|
583
|
+
.option('start-path', {
|
|
584
|
+
type: 'string',
|
|
585
|
+
description: 'Path to start on (appended to base URL, e.g., /dashboard)',
|
|
586
|
+
})
|
|
587
|
+
.option('debug-outcome', {
|
|
588
|
+
type: 'string',
|
|
589
|
+
hidden: true,
|
|
590
|
+
choices: [
|
|
591
|
+
'verified',
|
|
592
|
+
'partial',
|
|
593
|
+
'blocked',
|
|
594
|
+
'failed',
|
|
595
|
+
'incomplete',
|
|
596
|
+
],
|
|
597
|
+
});
|
|
598
|
+
}, async (argv) => {
|
|
599
|
+
await runGoCommand(argv, true);
|
|
600
|
+
})
|
|
601
|
+
.command('add env <env-name>', false, (yargs) => {
|
|
602
|
+
return yargs
|
|
603
|
+
.positional('env-name', {
|
|
604
|
+
type: 'string',
|
|
605
|
+
description: 'Name of the environment (e.g., local, staging, prod)',
|
|
606
|
+
demandOption: true,
|
|
607
|
+
})
|
|
608
|
+
.option('ci', {
|
|
609
|
+
type: 'boolean',
|
|
610
|
+
description: 'Create CI environment (encrypted auth, committed to git)',
|
|
611
|
+
default: false,
|
|
612
|
+
})
|
|
613
|
+
.option('skip-auth', {
|
|
614
|
+
type: 'boolean',
|
|
615
|
+
description: 'Skip browser authentication (just save URL and settings)',
|
|
616
|
+
default: false,
|
|
617
|
+
});
|
|
618
|
+
}, async (argv) => {
|
|
619
|
+
warnRenamed('ranger add env', 'ranger profile add');
|
|
620
|
+
await withTelemetry('add env', (telemetry) => addEnv(argv['env-name'], {
|
|
621
|
+
ci: argv.ci,
|
|
622
|
+
skipAuth: argv['skip-auth'],
|
|
623
|
+
}, telemetry));
|
|
624
|
+
})
|
|
625
|
+
.command('use <env-name>', false, (yargs) => {
|
|
626
|
+
return yargs.positional('env-name', {
|
|
627
|
+
type: 'string',
|
|
628
|
+
description: 'Name of the environment',
|
|
629
|
+
demandOption: true,
|
|
630
|
+
});
|
|
631
|
+
}, async (argv) => {
|
|
632
|
+
warnRenamed('ranger use', 'ranger profile use');
|
|
633
|
+
await withTelemetry('use', () => useEnv(argv['env-name']));
|
|
634
|
+
})
|
|
635
|
+
.command('config', false, (yargs) => {
|
|
636
|
+
return yargs
|
|
637
|
+
.command('set <env> <key> <value>', false, (yargs) => {
|
|
95
638
|
return yargs
|
|
96
639
|
.positional('env', {
|
|
97
640
|
type: 'string',
|
|
@@ -109,9 +652,10 @@ yargs(process.argv.slice(2))
|
|
|
109
652
|
demandOption: true,
|
|
110
653
|
});
|
|
111
654
|
}, async (argv) => {
|
|
112
|
-
|
|
655
|
+
warnRenamed('ranger config set', 'ranger profile config set');
|
|
656
|
+
await withTelemetry('config set', () => configSet(argv.env, argv.key, argv.value));
|
|
113
657
|
})
|
|
114
|
-
.command('get <env> <key>',
|
|
658
|
+
.command('get <env> <key>', false, (yargs) => {
|
|
115
659
|
return yargs
|
|
116
660
|
.positional('env', {
|
|
117
661
|
type: 'string',
|
|
@@ -124,18 +668,20 @@ yargs(process.argv.slice(2))
|
|
|
124
668
|
demandOption: true,
|
|
125
669
|
});
|
|
126
670
|
}, async (argv) => {
|
|
127
|
-
|
|
671
|
+
warnRenamed('ranger config get', 'ranger profile config get');
|
|
672
|
+
await withTelemetry('config get', () => configGet(argv.env, argv.key));
|
|
128
673
|
})
|
|
129
|
-
.command('list <env>',
|
|
674
|
+
.command('list <env>', false, (yargs) => {
|
|
130
675
|
return yargs.positional('env', {
|
|
131
676
|
type: 'string',
|
|
132
677
|
description: 'Environment name',
|
|
133
678
|
demandOption: true,
|
|
134
679
|
});
|
|
135
680
|
}, async (argv) => {
|
|
136
|
-
|
|
681
|
+
warnRenamed('ranger config list', 'ranger profile config list');
|
|
682
|
+
await withTelemetry('config list', () => configList(argv.env));
|
|
137
683
|
})
|
|
138
|
-
.command('unset <env> <key>',
|
|
684
|
+
.command('unset <env> <key>', false, (yargs) => {
|
|
139
685
|
return yargs
|
|
140
686
|
.positional('env', {
|
|
141
687
|
type: 'string',
|
|
@@ -148,339 +694,157 @@ yargs(process.argv.slice(2))
|
|
|
148
694
|
demandOption: true,
|
|
149
695
|
});
|
|
150
696
|
}, async (argv) => {
|
|
151
|
-
|
|
152
|
-
|
|
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);
|
|
697
|
+
warnRenamed('ranger config unset', 'ranger profile config unset');
|
|
698
|
+
await withTelemetry('config unset', () => configUnset(argv.env, argv.key));
|
|
175
699
|
})
|
|
176
|
-
.demandCommand(1, 'You must specify
|
|
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`);
|
|
700
|
+
.demandCommand(1, 'You must specify a config subcommand');
|
|
183
701
|
})
|
|
184
|
-
.command('env',
|
|
702
|
+
.command('env', false, (yargs) => {
|
|
185
703
|
return yargs
|
|
186
|
-
.command('ls',
|
|
187
|
-
|
|
704
|
+
.command('ls', false, () => { }, async () => {
|
|
705
|
+
warnRenamed('ranger env ls', 'ranger profile ls');
|
|
706
|
+
await withTelemetry('env list', () => envList());
|
|
188
707
|
})
|
|
189
|
-
.command('update <env-name>',
|
|
708
|
+
.command('update <env-name>', false, (yargs) => {
|
|
190
709
|
return yargs.positional('env-name', {
|
|
191
710
|
type: 'string',
|
|
192
711
|
description: 'Name of the environment to update',
|
|
193
712
|
demandOption: true,
|
|
194
713
|
});
|
|
195
714
|
}, async (argv) => {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
.demandCommand(1, 'You must specify an env subcommand, try ranger env help')
|
|
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();
|
|
209
|
-
})
|
|
210
|
-
.command('verify-in-browser', false, (yargs) => {
|
|
211
|
-
return yargs
|
|
212
|
-
.option('task', {
|
|
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)',
|
|
715
|
+
warnRenamed('ranger env update', 'ranger profile update');
|
|
716
|
+
await withTelemetry('env update', (telemetry) => updateEnv(argv['env-name'], telemetry));
|
|
220
717
|
})
|
|
221
|
-
.
|
|
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();
|
|
718
|
+
.demandCommand(1, 'You must specify an env subcommand');
|
|
272
719
|
})
|
|
273
|
-
.command('
|
|
274
|
-
await update();
|
|
275
|
-
})
|
|
276
|
-
.command('feature', 'Manage feature tracking', (yargs) => {
|
|
720
|
+
.command('feature', false, (yargs) => {
|
|
277
721
|
return yargs
|
|
278
|
-
.command('create <name>',
|
|
722
|
+
.command('create <name>', false, (yargs) => {
|
|
279
723
|
return yargs
|
|
280
724
|
.positional('name', {
|
|
281
725
|
type: 'string',
|
|
282
|
-
description: 'Feature name',
|
|
726
|
+
description: 'Feature review name',
|
|
283
727
|
demandOption: true,
|
|
284
728
|
})
|
|
285
729
|
.option('description', {
|
|
286
730
|
type: 'string',
|
|
287
731
|
alias: 'd',
|
|
288
|
-
description: 'Feature description',
|
|
732
|
+
description: 'Feature review description',
|
|
289
733
|
})
|
|
290
734
|
.option('checklist', {
|
|
291
735
|
type: 'array',
|
|
292
736
|
alias: 'c',
|
|
293
|
-
description: '
|
|
737
|
+
description: 'Scenarios (use multiple -c flags for multiple scenarios)',
|
|
738
|
+
})
|
|
739
|
+
.option('scenario', {
|
|
740
|
+
type: 'array',
|
|
741
|
+
hidden: true,
|
|
294
742
|
});
|
|
295
743
|
}, async (argv) => {
|
|
296
|
-
|
|
744
|
+
warnRenamed('ranger feature create', 'ranger create');
|
|
745
|
+
warnFlagRenamed('ranger create', '--checklist', '--scenario');
|
|
746
|
+
const scenarios = argv.scenario ||
|
|
747
|
+
argv.checklist;
|
|
748
|
+
await withTelemetry('feature create', () => featureCreate(argv.name, {
|
|
297
749
|
description: argv.description,
|
|
298
|
-
|
|
299
|
-
});
|
|
750
|
+
scenarios,
|
|
751
|
+
}));
|
|
300
752
|
})
|
|
301
|
-
.command('list',
|
|
753
|
+
.command('list', false, (yargs) => {
|
|
302
754
|
return yargs
|
|
303
755
|
.option('current-branch', {
|
|
304
756
|
type: 'boolean',
|
|
305
|
-
description: 'Filter to
|
|
757
|
+
description: 'Filter to feature reviews for current git branch',
|
|
306
758
|
})
|
|
307
759
|
.option('limit', {
|
|
308
760
|
type: 'number',
|
|
309
761
|
alias: 'l',
|
|
310
|
-
description: 'Maximum number of
|
|
762
|
+
description: 'Maximum number of feature reviews to return',
|
|
311
763
|
default: 10,
|
|
312
764
|
})
|
|
313
765
|
.option('offset', {
|
|
314
766
|
type: 'number',
|
|
315
767
|
alias: 'o',
|
|
316
|
-
description: 'Number of
|
|
768
|
+
description: 'Number of feature reviews to skip',
|
|
317
769
|
default: 0,
|
|
318
770
|
})
|
|
319
771
|
.option('include-deleted', {
|
|
320
772
|
type: 'boolean',
|
|
321
773
|
alias: 'd',
|
|
322
|
-
description: 'Include soft-deleted
|
|
774
|
+
description: 'Include soft-deleted feature reviews',
|
|
323
775
|
default: false,
|
|
324
776
|
});
|
|
325
777
|
}, async (argv) => {
|
|
326
|
-
|
|
778
|
+
warnRenamed('ranger feature list', 'ranger list');
|
|
779
|
+
await withTelemetry('feature list', () => featureList({
|
|
327
780
|
currentBranch: argv['current-branch'],
|
|
328
781
|
limit: argv.limit,
|
|
329
782
|
offset: argv.offset,
|
|
330
783
|
includeDeleted: argv['include-deleted'],
|
|
331
|
-
});
|
|
332
|
-
})
|
|
333
|
-
.command('show [id]', 'Show feature details (uses active feature if no id)', (yargs) => {
|
|
334
|
-
return yargs.positional('id', {
|
|
335
|
-
type: 'string',
|
|
336
|
-
description: 'Feature ID',
|
|
337
|
-
});
|
|
338
|
-
}, async (argv) => {
|
|
339
|
-
await featureShow(argv.id);
|
|
784
|
+
}));
|
|
340
785
|
})
|
|
341
|
-
.command('
|
|
786
|
+
.command('show [id]', false, (yargs) => {
|
|
342
787
|
return yargs.positional('id', {
|
|
343
788
|
type: 'string',
|
|
344
|
-
description: 'Feature ID
|
|
789
|
+
description: 'Feature review ID',
|
|
345
790
|
});
|
|
346
791
|
}, async (argv) => {
|
|
347
|
-
|
|
792
|
+
warnRenamed('ranger feature show', 'ranger show');
|
|
793
|
+
await withTelemetry('feature show', () => featureShow(argv.id));
|
|
348
794
|
})
|
|
349
|
-
.command('
|
|
795
|
+
.command('resume [id]', false, (yargs) => {
|
|
350
796
|
return yargs.positional('id', {
|
|
351
797
|
type: 'string',
|
|
352
|
-
description: 'Feature ID (
|
|
798
|
+
description: 'Feature review ID (optional - bypasses search/prompt)',
|
|
353
799
|
});
|
|
354
800
|
}, async (argv) => {
|
|
355
|
-
|
|
801
|
+
warnRenamed('ranger feature resume', 'ranger resume');
|
|
802
|
+
await withTelemetry('feature resume', () => featureResume(argv.id));
|
|
356
803
|
})
|
|
357
|
-
.command('
|
|
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) => {
|
|
804
|
+
.command('add-checklist-item <description>', false, (yargs) => {
|
|
366
805
|
return yargs
|
|
367
806
|
.positional('description', {
|
|
368
807
|
type: 'string',
|
|
369
|
-
description: '
|
|
808
|
+
description: 'Scenario description',
|
|
370
809
|
demandOption: true,
|
|
371
810
|
})
|
|
372
811
|
.option('id', {
|
|
373
812
|
type: 'string',
|
|
374
|
-
description: 'Feature ID (uses active feature if not provided)',
|
|
813
|
+
description: 'Feature review ID (uses active feature review if not provided)',
|
|
375
814
|
});
|
|
376
815
|
}, async (argv) => {
|
|
377
|
-
|
|
816
|
+
warnRenamed('ranger feature add-checklist-item', 'ranger add-scenario');
|
|
817
|
+
await withTelemetry('feature add-checklist-item', () => featureAddScenario(argv.description, argv.id));
|
|
378
818
|
})
|
|
379
|
-
.command('get-feedback [id]',
|
|
819
|
+
.command('get-feedback [id]', false, (yargs) => {
|
|
380
820
|
return yargs.positional('id', {
|
|
381
821
|
type: 'string',
|
|
382
|
-
description: 'Feature ID (uses active feature if not provided)',
|
|
822
|
+
description: 'Feature review ID (uses active feature review if not provided)',
|
|
383
823
|
});
|
|
384
824
|
}, async (argv) => {
|
|
385
|
-
|
|
825
|
+
warnRenamed('ranger feature get-feedback', 'ranger get-review');
|
|
826
|
+
await withTelemetry('feature get-feedback', () => featureGetReview(argv.id));
|
|
386
827
|
})
|
|
387
|
-
.command('delete [id]',
|
|
828
|
+
.command('delete [id]', false, (yargs) => {
|
|
388
829
|
return yargs.positional('id', {
|
|
389
830
|
type: 'string',
|
|
390
|
-
description: 'Feature ID',
|
|
831
|
+
description: 'Feature review ID',
|
|
391
832
|
});
|
|
392
833
|
}, async (argv) => {
|
|
393
|
-
|
|
834
|
+
warnRenamed('ranger feature delete', 'ranger delete');
|
|
835
|
+
await withTelemetry('feature delete', () => featureDelete(argv.id));
|
|
394
836
|
})
|
|
395
|
-
.command('restore <id>',
|
|
837
|
+
.command('restore <id>', false, (yargs) => {
|
|
396
838
|
return yargs.positional('id', {
|
|
397
839
|
type: 'string',
|
|
398
|
-
description: 'Feature ID to restore',
|
|
840
|
+
description: 'Feature review ID to restore',
|
|
399
841
|
demandOption: true,
|
|
400
842
|
});
|
|
401
843
|
}, 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)',
|
|
437
|
-
})
|
|
438
|
-
.option('item', {
|
|
439
|
-
type: 'number',
|
|
440
|
-
description: 'Checklist item index (1-based)',
|
|
844
|
+
warnRenamed('ranger feature restore', 'ranger restore');
|
|
845
|
+
await withTelemetry('feature restore', () => featureRestore(argv.id));
|
|
441
846
|
})
|
|
442
|
-
.
|
|
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);
|
|
847
|
+
.demandCommand(1, 'You must specify a feature review subcommand');
|
|
484
848
|
})
|
|
485
849
|
// Unified hook command (hidden from help - invoked automatically by Claude Code / OpenCode plugins)
|
|
486
850
|
.command('hook', false, (yargs) => {
|
|
@@ -535,8 +899,16 @@ Examples:
|
|
|
535
899
|
errorMessage: msg || err?.message,
|
|
536
900
|
errorType,
|
|
537
901
|
});
|
|
902
|
+
// Report to telemetry if there's an active collector
|
|
903
|
+
const collector = getCurrentCollector();
|
|
904
|
+
if (collector) {
|
|
905
|
+
await collector.trackCommandError(err || new Error(msg || 'Unknown error'));
|
|
906
|
+
}
|
|
538
907
|
process.exit(1);
|
|
539
908
|
})
|
|
909
|
+
.epilogue('Documentation:\n' +
|
|
910
|
+
' https://docs.ranger.net\n' +
|
|
911
|
+
' https://docs.ranger.net/llms.txt (for LLMs)')
|
|
540
912
|
.help()
|
|
541
913
|
.alias('help', 'h')
|
|
542
914
|
.parse();
|