@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.
Files changed (90) hide show
  1. package/README.md +47 -45
  2. package/build/cli.js +649 -277
  3. package/build/cli.js.map +1 -1
  4. package/build/commands/addEnv.js +1 -1
  5. package/build/commands/addEnv.js.map +1 -1
  6. package/build/commands/authEncrypt.js +5 -10
  7. package/build/commands/authEncrypt.js.map +1 -1
  8. package/build/commands/clean.js +1 -1
  9. package/build/commands/clean.js.map +1 -1
  10. package/build/commands/config.js +9 -15
  11. package/build/commands/config.js.map +1 -1
  12. package/build/commands/env.js +10 -13
  13. package/build/commands/env.js.map +1 -1
  14. package/build/commands/feature.js +174 -123
  15. package/build/commands/feature.js.map +1 -1
  16. package/build/commands/hooks/autoPrompt.js +1 -1
  17. package/build/commands/hooks/disable.js +1 -1
  18. package/build/commands/hooks/enable.js +9 -4
  19. package/build/commands/hooks/enable.js.map +1 -1
  20. package/build/commands/hooks/exitPlanMode.js +8 -8
  21. package/build/commands/hooks/planReminder.js +7 -7
  22. package/build/commands/hooks/planStart.js +4 -4
  23. package/build/commands/hooks/postEdit.js +4 -4
  24. package/build/commands/hooks/postEdit.js.map +1 -1
  25. package/build/commands/hooks/preCompact.js +3 -3
  26. package/build/commands/hooks/preCompact.js.map +1 -1
  27. package/build/commands/hooks/sessionStart.js +19 -5
  28. package/build/commands/hooks/sessionStart.js.map +1 -1
  29. package/build/commands/hooks/stopHook.js +114 -20
  30. package/build/commands/hooks/stopHook.js.map +1 -1
  31. package/build/commands/index.js +1 -2
  32. package/build/commands/index.js.map +1 -1
  33. package/build/commands/login.js +2 -5
  34. package/build/commands/login.js.map +1 -1
  35. package/build/commands/setupCi.js +189 -0
  36. package/build/commands/setupCi.js.map +1 -0
  37. package/build/commands/skillup.js +16 -68
  38. package/build/commands/skillup.js.map +1 -1
  39. package/build/commands/start.js +1 -1
  40. package/build/commands/start.js.map +1 -1
  41. package/build/commands/status.js +14 -13
  42. package/build/commands/status.js.map +1 -1
  43. package/build/commands/update.js +52 -5
  44. package/build/commands/update.js.map +1 -1
  45. package/build/commands/updateEnv.js +1 -1
  46. package/build/commands/updateEnv.js.map +1 -1
  47. package/build/commands/useEnv.js +1 -1
  48. package/build/commands/useEnv.js.map +1 -1
  49. package/build/commands/utils/activeProfile.js +76 -0
  50. package/build/commands/utils/activeProfile.js.map +1 -0
  51. package/build/commands/utils/browserSessionsApi.js +1 -1
  52. package/build/commands/utils/browserSessionsApi.js.map +1 -1
  53. package/build/commands/utils/deviceAuth.js +53 -5
  54. package/build/commands/utils/deviceAuth.js.map +1 -1
  55. package/build/commands/utils/environment.js +11 -12
  56. package/build/commands/utils/environment.js.map +1 -1
  57. package/build/commands/utils/featureApi.js +55 -30
  58. package/build/commands/utils/featureApi.js.map +1 -1
  59. package/build/commands/utils/featureReportGenerator.js +6 -6
  60. package/build/commands/utils/featureReportGenerator.js.map +1 -1
  61. package/build/commands/utils/keychain.js +1 -1
  62. package/build/commands/utils/localAgentInstallationsApi.js +1 -1
  63. package/build/commands/utils/profileMessages.js +8 -0
  64. package/build/commands/utils/profileMessages.js.map +1 -0
  65. package/build/commands/utils/profileSetupBanner.js +167 -0
  66. package/build/commands/utils/profileSetupBanner.js.map +1 -0
  67. package/build/commands/utils/settings.js +20 -2
  68. package/build/commands/utils/settings.js.map +1 -1
  69. package/build/commands/utils/skills.js +1 -1
  70. package/build/commands/utils/telemetry.js +254 -0
  71. package/build/commands/utils/telemetry.js.map +1 -0
  72. package/build/commands/utils/userApi.js +4 -4
  73. package/build/commands/utils/userApi.js.map +1 -1
  74. package/build/commands/verifyFeature.js +816 -526
  75. package/build/commands/verifyFeature.js.map +1 -1
  76. package/build/commands/verifyInBrowser.js +1 -1
  77. package/build/commands/verifyInBrowser.js.map +1 -1
  78. package/build/skills/ranger/SKILL.md +65 -64
  79. package/build/skills/ranger/create.md +31 -31
  80. package/build/skills/ranger/feedback.md +25 -17
  81. package/build/skills/ranger/start.md +37 -37
  82. package/build/skills/ranger/verify.md +59 -55
  83. package/package.json +1 -1
  84. package/scripts/postinstall.js +1 -1
  85. package/build/commands/dataMcpServer.js +0 -1
  86. package/build/commands/dataMcpServer.js.map +0 -1
  87. package/build/commands/utils/cliSecret.js +0 -1
  88. package/build/commands/utils/cliSecret.js.map +0 -1
  89. package/build/skills/bug-bash.md +0 -329
  90. 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, featureSessions, featureConcludeSession, featureAddChecklistItem, featureGetFeedback, featureDelete, featureRestore, } from './commands/feature.js';
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('add env <env-name>', 'Add environment configuration', (yargs) => {
158
+ .command('create <name>', 'Create a new feature review with scenarios', (yargs) => {
36
159
  return yargs
37
- .positional('env-name', {
160
+ .positional('name', {
38
161
  type: 'string',
39
- description: 'Name of the environment (e.g., local, staging, prod)',
162
+ description: 'Feature review name',
40
163
  demandOption: true,
41
164
  })
42
- .option('ci', {
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: 'Create CI environment (encrypted auth, committed to git)',
45
- default: false,
192
+ description: 'Filter to feature reviews for current git branch',
46
193
  })
47
- .option('skip-auth', {
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
- description: 'Skip browser authentication (just save URL and settings)',
208
+ alias: 'd',
209
+ description: 'Include soft-deleted feature reviews',
50
210
  default: false,
51
211
  });
52
212
  }, async (argv) => {
53
- await addEnv(argv['env-name'], {
54
- ci: argv.ci,
55
- skipAuth: argv['skip-auth'],
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('use <env-name>', 'Switch to using a specific environment', (yargs) => {
59
- return yargs.positional('env-name', {
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: 'Name of the environment',
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 useEnv(argv['env-name']);
248
+ await withTelemetry('add-scenario', () => featureAddScenario(argv.description, argv.id));
66
249
  })
67
- .command('start [token]', 'Initialize Ranger in your project', (yargs) => {
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('config', 'Manage environment configuration', (yargs) => {
349
+ .command('profile', 'Manage profiles', (yargs) => {
93
350
  return yargs
94
- .command('set <env> <key> <value>', 'Set a config value', (yargs) => {
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
- await configSet(argv.env, argv.key, argv.value);
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>', 'Get a config value', (yargs) => {
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
- await configGet(argv.env, argv.key);
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>', 'List all config for an environment', (yargs) => {
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
- await configList(argv.env);
681
+ warnRenamed('ranger config list', 'ranger profile config list');
682
+ await withTelemetry('config list', () => configList(argv.env));
137
683
  })
138
- .command('unset <env> <key>', 'Remove a config value', (yargs) => {
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
- await configUnset(argv.env, argv.key);
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 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`);
700
+ .demandCommand(1, 'You must specify a config subcommand');
183
701
  })
184
- .command('env', 'Manage environments', (yargs) => {
702
+ .command('env', false, (yargs) => {
185
703
  return yargs
186
- .command('ls', 'List all environments', () => { }, async () => {
187
- await envList();
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>', 'Update authentication for an existing environment', (yargs) => {
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
- await updateEnv(argv['env-name']);
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
- .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();
718
+ .demandCommand(1, 'You must specify an env subcommand');
272
719
  })
273
- .command('update', 'Update Ranger CLI to the latest version', () => { }, async () => {
274
- await update();
275
- })
276
- .command('feature', 'Manage feature tracking', (yargs) => {
720
+ .command('feature', false, (yargs) => {
277
721
  return yargs
278
- .command('create <name>', 'Create a new feature with checklist', (yargs) => {
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: 'Checklist items (use multiple -c flags for multiple items)',
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
- await featureCreate(argv.name, {
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
- checklist: argv.checklist,
299
- });
750
+ scenarios,
751
+ }));
300
752
  })
301
- .command('list', 'List all features', (yargs) => {
753
+ .command('list', false, (yargs) => {
302
754
  return yargs
303
755
  .option('current-branch', {
304
756
  type: 'boolean',
305
- description: 'Filter to features for current git branch',
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 features to return',
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 features to skip',
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 features',
774
+ description: 'Include soft-deleted feature reviews',
323
775
  default: false,
324
776
  });
325
777
  }, async (argv) => {
326
- await featureList({
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('resume [id]', 'Find and use feature matching current git context', (yargs) => {
786
+ .command('show [id]', false, (yargs) => {
342
787
  return yargs.positional('id', {
343
788
  type: 'string',
344
- description: 'Feature ID (optional - bypasses search/prompt)',
789
+ description: 'Feature review ID',
345
790
  });
346
791
  }, async (argv) => {
347
- await featureResume(argv.id);
792
+ warnRenamed('ranger feature show', 'ranger show');
793
+ await withTelemetry('feature show', () => featureShow(argv.id));
348
794
  })
349
- .command('sessions [id]', 'List sessions for a feature', (yargs) => {
795
+ .command('resume [id]', false, (yargs) => {
350
796
  return yargs.positional('id', {
351
797
  type: 'string',
352
- description: 'Feature ID (uses active feature if not provided)',
798
+ description: 'Feature review ID (optional - bypasses search/prompt)',
353
799
  });
354
800
  }, async (argv) => {
355
- await featureSessions(argv.id);
801
+ warnRenamed('ranger feature resume', 'ranger resume');
802
+ await withTelemetry('feature resume', () => featureResume(argv.id));
356
803
  })
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) => {
804
+ .command('add-checklist-item <description>', false, (yargs) => {
366
805
  return yargs
367
806
  .positional('description', {
368
807
  type: 'string',
369
- description: 'Checklist item 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
- await featureAddChecklistItem(argv.description, argv.id);
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]', 'Show reviewer feedback (comments) for all action items', (yargs) => {
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
- await featureGetFeedback(argv.id);
825
+ warnRenamed('ranger feature get-feedback', 'ranger get-review');
826
+ await withTelemetry('feature get-feedback', () => featureGetReview(argv.id));
386
827
  })
387
- .command('delete [id]', 'Soft delete a feature (uses active feature if no id)', (yargs) => {
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
- await featureDelete(argv.id);
834
+ warnRenamed('ranger feature delete', 'ranger delete');
835
+ await withTelemetry('feature delete', () => featureDelete(argv.id));
394
836
  })
395
- .command('restore <id>', 'Restore a soft-deleted feature', (yargs) => {
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
- await featureRestore(argv.id);
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
- .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);
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();