Skip to content

Commit 3da69cb

Browse files
authored
feat: improve CLI help structure (#238)
* feat: improve CLI help structure * refactor: simplify CLI help schema * fix: preserve required top-level help syntax * fix: document batch step source in top-level help
1 parent 5c1601c commit 3da69cb

2 files changed

Lines changed: 282 additions & 93 deletions

File tree

src/utils/__tests__/args.test.ts

Lines changed: 86 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -456,31 +456,65 @@ test('parseArgs rejects invalid swipe pattern', () => {
456456
);
457457
});
458458

459-
test('usage includes --relaunch flag', () => {
460-
assert.match(usage(), /--relaunch/);
461-
assert.match(usage(), /install-from-source <url>/);
462-
assert.match(usage(), /metro prepare/);
463-
assert.match(usage(), /--header <name:value>/);
464-
assert.match(usage(), /--public-base-url <url>/);
465-
assert.match(usage(), /--restart/);
466-
assert.match(usage(), /--target mobile\|tv/);
467-
assert.match(usage(), /--ios-simulator-device-set <path>/);
468-
assert.match(usage(), /--android-device-allowlist <serials>/);
469-
assert.match(usage(), /--fps <n>/);
470-
assert.match(usage(), /network dump/);
471-
assert.match(usage(), /--save-script \[path\]/);
472-
assert.match(usage(), /clipboard read \| clipboard write <text>/);
473-
assert.match(usage(), /keyboard \[status\|get\|dismiss\]/);
474-
assert.match(usage(), /trigger-app-event <event> \[payloadJson\]/);
475-
assert.match(usage(), /pinch <scale> \[x\] \[y\]/);
476-
assert.match(usage(), /--state-dir <path>/);
477-
assert.match(usage(), /--daemon-transport auto\|socket\|http/);
478-
assert.match(usage(), /--daemon-server-mode socket\|http\|dual/);
479-
assert.match(usage(), /--tenant <id>/);
480-
assert.match(usage(), /--session-isolation none\|tenant/);
481-
assert.match(usage(), /--run-id <id>/);
482-
assert.match(usage(), /--lease-id <id>/);
483-
assert.doesNotMatch(usage(), /--metadata/);
459+
test('usage includes concise top-level commands', () => {
460+
const usageText = usage();
461+
assert.match(usageText, /install-from-source <url>/);
462+
assert.match(usageText, /metro prepare --public-base-url <url>/);
463+
assert.match(usageText, /batch --steps <json> \| --steps-file <path>/);
464+
assert.match(usageText, /network dump/);
465+
assert.match(usageText, /clipboard read \| clipboard write <text>/);
466+
assert.match(usageText, /keyboard \[action\]/);
467+
assert.match(usageText, /trigger-app-event <event> \[payloadJson\]/);
468+
assert.match(usageText, /pinch <scale> \[x\] \[y\]/);
469+
assert.match(usageText, /record start \[path\] \| record stop/);
470+
assert.match(usageText, /trace start \[path\] \| trace stop/);
471+
});
472+
473+
test('usage includes only global flags in the top-level flags section', () => {
474+
const usageText = usage();
475+
assert.match(usageText, /--target mobile\|tv/);
476+
assert.match(usageText, /--ios-simulator-device-set <path>/);
477+
assert.match(usageText, /--android-device-allowlist <serials>/);
478+
assert.match(usageText, /--state-dir <path>/);
479+
assert.match(usageText, /--daemon-transport auto\|socket\|http/);
480+
assert.match(usageText, /--daemon-server-mode socket\|http\|dual/);
481+
assert.match(usageText, /--tenant <id>/);
482+
assert.match(usageText, /--session-isolation none\|tenant/);
483+
assert.match(usageText, /--run-id <id>/);
484+
assert.match(usageText, /--lease-id <id>/);
485+
assert.doesNotMatch(usageText, /--relaunch/);
486+
assert.doesNotMatch(usageText, /--header <name:value>/);
487+
assert.doesNotMatch(usageText, /--restart/);
488+
assert.doesNotMatch(usageText, /--fps <n>/);
489+
assert.doesNotMatch(usageText, /--save-script \[path\]/);
490+
assert.doesNotMatch(usageText, /--metadata/);
491+
});
492+
493+
test('usage includes skills, config, environment, and examples footers', () => {
494+
const usageText = usage();
495+
assert.match(usageText, /Agent Skills:/);
496+
assert.match(usageText, /agent-device\s+Canonical mobile automation flows/);
497+
assert.match(usageText, /dogfood\s+Exploratory QA and bug hunts/);
498+
assert.match(usageText, /See `skills\/<name>\/SKILL\.md` in the installed package\./);
499+
assert.match(usageText, /Configuration:/);
500+
assert.match(
501+
usageText,
502+
/Default config files: ~\/\.agent-device\/config\.json, \.\/agent-device\.json/,
503+
);
504+
assert.match(
505+
usageText,
506+
/Use --config <path> or AGENT_DEVICE_CONFIG to load one explicit config file\./,
507+
);
508+
assert.match(usageText, /Environment:/);
509+
assert.match(usageText, /AGENT_DEVICE_SESSION\s+Default session name/);
510+
assert.match(usageText, /AGENT_DEVICE_PLATFORM\s+Default platform binding/);
511+
assert.match(usageText, /AGENT_DEVICE_SESSION_LOCK\s+Bound-session conflict mode/);
512+
assert.match(usageText, /AGENT_DEVICE_DAEMON_BASE_URL\s+Connect to remote daemon/);
513+
assert.match(usageText, /Examples:/);
514+
assert.match(usageText, /agent-device open Settings --platform ios/);
515+
assert.match(usageText, /agent-device snapshot -i/);
516+
assert.match(usageText, /agent-device fill @e3 "test@example\.com"/);
517+
assert.match(usageText, /agent-device replay \.\/session\.ad/);
484518
});
485519

486520
test('apps defaults to --all filter and allows overrides', () => {
@@ -617,13 +651,25 @@ test('invalid range errors are deterministic', () => {
617651

618652
test('usage includes swipe and press series options', () => {
619653
const help = usage();
620-
assert.match(help, /diff snapshot/);
654+
assert.match(help, /diff <kind>/);
621655
assert.match(help, /swipe <x1> <y1> <x2> <y2>/);
622-
assert.match(help, /--pattern one-way\|ping-pong/);
623-
assert.match(help, /--interval-ms/);
624-
assert.match(help, /settings <wifi\|airplane\|location> <on\|off>/);
625-
assert.match(help, /settings appearance <light\|dark\|toggle>/);
626-
assert.match(help, /settings permission <grant\|deny\|reset>/);
656+
assert.match(help, /settings \[area\] \[options\]/);
657+
assert.doesNotMatch(help, /--pattern one-way\|ping-pong/);
658+
assert.doesNotMatch(help, /--interval-ms/);
659+
});
660+
661+
test('usage renders concise commands inline with descriptions', () => {
662+
const help = usage();
663+
assert.match(help, /Commands:[\s\S]*\n boot\s{2,}Boot target device\/simulator/);
664+
assert.match(help, / metro prepare --public-base-url <url>\s{2,}Prepare local Metro runtime/);
665+
assert.match(help, / batch --steps <json> \| --steps-file <path>\s{2,}Run multiple commands/);
666+
assert.match(help, / session list\s{2,}List active sessions/);
667+
assert.doesNotMatch(help, / metro prepare[^\n]*--project-root/);
668+
assert.doesNotMatch(help, /\n batch\s{2,}Run multiple commands/);
669+
assert.doesNotMatch(
670+
help,
671+
/Prepare a local Metro runtime and optionally bridge it through agent-device-proxy/,
672+
);
627673
});
628674

629675
test('command usage shows command and global flags separately', () => {
@@ -636,6 +682,15 @@ test('command usage shows command and global flags separately', () => {
636682
assert.match(help, /--platform ios\|android\|apple/);
637683
});
638684

685+
test('command usage keeps detailed descriptions', () => {
686+
const help = usageForCommand('metro');
687+
if (help === null) throw new Error('Expected command help text');
688+
assert.match(
689+
help,
690+
/Prepare a local Metro runtime and optionally bridge it through agent-device-proxy/,
691+
);
692+
});
693+
639694
test('command usage shows no command flags when unsupported', () => {
640695
const help = usageForCommand('appstate');
641696
if (help === null) throw new Error('Expected command help text');

0 commit comments

Comments
 (0)