scenescout 3.14.0 → 3.14.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/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/dist/cli.js +31 -69
- package/dist/commands.js +104 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# scenescout
|
|
2
2
|
|
|
3
|
+
## 3.14.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5358a81: `--help` and `-h` now print the usage and exit 0 on every subcommand before it does anything; `scenescout install --help` used to run a real install. `install`, `doctor`, `scan`, `status` and `watch` now refuse a flag or argument they do not know instead of ignoring it.
|
|
8
|
+
|
|
3
9
|
## 3.14.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Works with Claude Code · Cursor · VS Code (Copilot) · Codex CLI · Gemini CLI
|
|
|
12
12
|

|
|
13
13
|

|
|
14
14
|
|
|
15
|
-
[👀 See it work](#-see-it-work) · [✨ Why](#-why-its-different) · [🎯 Two ways to use it](#-two-ways-to-use-it) · [🚀 Quickstart](#-quickstart) · [🧰 Toolbox](#-the-toolbox) · [🔌 Other clients](#-other-mcp-clients) · [🔒 Safety](#-safety-model) · [🩺 Troubleshooting](#-troubleshooting)
|
|
15
|
+
[📖 Guide](docs/guide/Home.md) · [👀 See it work](#-see-it-work) · [✨ Why](#-why-its-different) · [🎯 Two ways to use it](#-two-ways-to-use-it) · [🚀 Quickstart](#-quickstart) · [🧰 Toolbox](#-the-toolbox) · [🔌 Other clients](#-other-mcp-clients) · [🔒 Safety](#-safety-model) · [🩺 Troubleshooting](#-troubleshooting)
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
package/dist/cli.js
CHANGED
|
@@ -29,6 +29,7 @@ import { EXIT, exitCodeOf, formatCheck, parseCheckArgs, refusedFlowReason, toSar
|
|
|
29
29
|
import { LEGACY_MEMORY_DIRNAME, MEMORY_DIRNAME, writeSelfIgnore } from "./engine/memory.js";
|
|
30
30
|
import { formatStatus, liveEngines, liveTokenFileName, localClock, LIVE_TOKEN_FILE, pidAlive, watchTarget, wholeSessions, } from "./engine/live.js";
|
|
31
31
|
import { formatScan, scanProject } from "./scan.js";
|
|
32
|
+
import { dispatch } from "./commands.js";
|
|
32
33
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
33
34
|
const packageRoot = path.resolve(here, "..");
|
|
34
35
|
/** This package's version, as published. */
|
|
@@ -295,13 +296,6 @@ function flagValue(flags, name) {
|
|
|
295
296
|
const next = flags[at + 1];
|
|
296
297
|
return next === undefined || next.startsWith("--") ? "" : next;
|
|
297
298
|
}
|
|
298
|
-
function browsersFlag(flags) {
|
|
299
|
-
// `--browser-only` is a different flag; a bare `--browser` is a slip that would otherwise be ignored and download Chromium.
|
|
300
|
-
const slip = flags.find((f) => f === "--browser" || f.startsWith("--browser="));
|
|
301
|
-
if (slip)
|
|
302
|
-
throw new Error(`unknown flag ${slip.split("=")[0]} — did you mean --browsers?`);
|
|
303
|
-
return flagValue(flags, "--browsers");
|
|
304
|
-
}
|
|
305
299
|
/** The `code` command on PATH, with its real path: the real path is what tells VS Code from a fork. */
|
|
306
300
|
function codeOnPath() {
|
|
307
301
|
const names = process.platform === "win32" ? ["code.cmd", "code.exe"] : ["code"];
|
|
@@ -349,7 +343,7 @@ async function install(flags) {
|
|
|
349
343
|
// only thing it cannot bring is the browser download.
|
|
350
344
|
const browserOnly = flags.includes("--browser-only");
|
|
351
345
|
// Read the choice before doing anything, so a typo costs nothing.
|
|
352
|
-
const selection = parseBrowserSelection(
|
|
346
|
+
const selection = parseBrowserSelection(flagValue(flags, "--browsers"));
|
|
353
347
|
if ("error" in selection)
|
|
354
348
|
throw new Error(selection.error);
|
|
355
349
|
const chosen = parseClients(flagValues(flags, ["--client", "--clients"]));
|
|
@@ -520,8 +514,6 @@ async function doctor(flags) {
|
|
|
520
514
|
}
|
|
521
515
|
/** `scenescout check`: exit 0 passed, 1 failed the gate, 2 could not run. */
|
|
522
516
|
async function check(args) {
|
|
523
|
-
if (args.includes("--help") || args.includes("-h"))
|
|
524
|
-
usage(0);
|
|
525
517
|
const parsed = parseCheckArgs(args, process.cwd());
|
|
526
518
|
if (!parsed.ok) {
|
|
527
519
|
console.error(`scenescout check: ${parsed.error}`);
|
|
@@ -584,8 +576,6 @@ async function check(args) {
|
|
|
584
576
|
}
|
|
585
577
|
/** `scenescout ci`: exit 0 when the run ran, 2 when it could not. Findings never change the exit code. */
|
|
586
578
|
async function ci(args) {
|
|
587
|
-
if (args.includes("--help") || args.includes("-h"))
|
|
588
|
-
usage(0);
|
|
589
579
|
const secrets = secretValues(process.env);
|
|
590
580
|
const say = (line) => console.log(redactKeys(line, secrets));
|
|
591
581
|
const fail = (message) => {
|
|
@@ -627,8 +617,6 @@ async function ci(args) {
|
|
|
627
617
|
}
|
|
628
618
|
/** `scenescout login`: exit 0 saved, 1 nothing saved. */
|
|
629
619
|
async function login(args) {
|
|
630
|
-
if (args.includes("--help") || args.includes("-h"))
|
|
631
|
-
usage(0);
|
|
632
620
|
const parsed = parseLoginArgs(args, process.cwd());
|
|
633
621
|
if (!parsed.ok) {
|
|
634
622
|
console.error(`scenescout login: ${parsed.error}`);
|
|
@@ -672,61 +660,35 @@ const [, , command, ...args] = process.argv;
|
|
|
672
660
|
// one line the user needs. `serve` is deliberately outside this: it hands off
|
|
673
661
|
// to the MCP server, whose own transport owns error reporting from then on.
|
|
674
662
|
try {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
break;
|
|
705
|
-
}
|
|
706
|
-
case "check": {
|
|
707
|
-
await check(args);
|
|
708
|
-
break;
|
|
709
|
-
}
|
|
710
|
-
case "ci": {
|
|
711
|
-
await ci(args);
|
|
712
|
-
break;
|
|
713
|
-
}
|
|
714
|
-
case "login": {
|
|
715
|
-
await login(args);
|
|
716
|
-
break;
|
|
717
|
-
}
|
|
718
|
-
case "status": {
|
|
719
|
-
status(path.resolve(args[0] ?? process.cwd()));
|
|
720
|
-
break;
|
|
721
|
-
}
|
|
722
|
-
case "watch": {
|
|
723
|
-
const positional = args.filter((a) => !a.startsWith("--"));
|
|
724
|
-
watch(path.resolve(positional[0] ?? process.cwd()), !args.includes("--no-open"));
|
|
725
|
-
break;
|
|
726
|
-
}
|
|
727
|
-
default:
|
|
728
|
-
usage();
|
|
729
|
-
}
|
|
663
|
+
await dispatch(command, args, {
|
|
664
|
+
usage,
|
|
665
|
+
version: () => console.log(packageVersion()),
|
|
666
|
+
refuse: (message) => {
|
|
667
|
+
console.error(`scenescout ${command}: ${message}`);
|
|
668
|
+
console.error("Run `scenescout --help` for the options.");
|
|
669
|
+
process.exit(1);
|
|
670
|
+
},
|
|
671
|
+
commands: {
|
|
672
|
+
scan: (a) => {
|
|
673
|
+
if (!a[0])
|
|
674
|
+
usage();
|
|
675
|
+
console.log(formatScan(scanProject(a[0])));
|
|
676
|
+
},
|
|
677
|
+
serve: async () => {
|
|
678
|
+
await import("./mcp-server.js");
|
|
679
|
+
},
|
|
680
|
+
install,
|
|
681
|
+
doctor,
|
|
682
|
+
check,
|
|
683
|
+
ci,
|
|
684
|
+
login,
|
|
685
|
+
status: (a) => status(path.resolve(a[0] ?? process.cwd())),
|
|
686
|
+
watch: (a) => {
|
|
687
|
+
const positional = a.filter((x) => !x.startsWith("--"));
|
|
688
|
+
watch(path.resolve(positional[0] ?? process.cwd()), !a.includes("--no-open"));
|
|
689
|
+
},
|
|
690
|
+
},
|
|
691
|
+
});
|
|
730
692
|
}
|
|
731
693
|
catch (err) {
|
|
732
694
|
console.error(`scenescout ${command ?? ""}: ${err instanceof Error ? err.message : String(err)}`);
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the CLI decides what a command line asks for, before any command runs.
|
|
3
|
+
*
|
|
4
|
+
* It lives apart from cli.ts so it can be table-tested: `scenescout install
|
|
5
|
+
* --help` once ran a real install, because install read only the flags it knew
|
|
6
|
+
* and ignored the rest. Every subcommand now answers `--help` / `-h` with the
|
|
7
|
+
* usage text and exit 0 before it does anything, and a command that parses its
|
|
8
|
+
* own flags by hand refuses one it does not know.
|
|
9
|
+
*/
|
|
10
|
+
const HANDLERS_OF = {
|
|
11
|
+
scan: true,
|
|
12
|
+
serve: true,
|
|
13
|
+
install: true,
|
|
14
|
+
doctor: true,
|
|
15
|
+
check: true,
|
|
16
|
+
ci: true,
|
|
17
|
+
login: true,
|
|
18
|
+
status: true,
|
|
19
|
+
watch: true,
|
|
20
|
+
};
|
|
21
|
+
export const SUBCOMMANDS = Object.keys(HANDLERS_OF);
|
|
22
|
+
/**
|
|
23
|
+
* Commands that read their arguments by hand. `check`, `ci` and `login` are
|
|
24
|
+
* absent: their own parsers refuse unknown options. `serve` is absent on
|
|
25
|
+
* purpose: it is the line an MCP client launches, and a stray argument there
|
|
26
|
+
* should not stop the server from starting.
|
|
27
|
+
*/
|
|
28
|
+
export const HAND_PARSED = {
|
|
29
|
+
install: {
|
|
30
|
+
switches: ["--skip-browser", "--no-register", "--no-command", "--browser-only"],
|
|
31
|
+
valued: ["--browsers", "--client", "--clients"],
|
|
32
|
+
// `--browser` is what `check` and `login` call it; for install it would otherwise download Chromium regardless.
|
|
33
|
+
hints: { "--browser": "--browsers" },
|
|
34
|
+
positional: 0,
|
|
35
|
+
},
|
|
36
|
+
doctor: { switches: ["--engine"], valued: [], positional: 0 },
|
|
37
|
+
scan: { switches: [], valued: [], positional: 1 },
|
|
38
|
+
status: { switches: [], valued: [], positional: 1 },
|
|
39
|
+
watch: { switches: ["--no-open"], valued: [], positional: 1 },
|
|
40
|
+
};
|
|
41
|
+
/** True when the arguments ask for help. */
|
|
42
|
+
export function wantsHelp(args) {
|
|
43
|
+
return args.includes("--help") || args.includes("-h");
|
|
44
|
+
}
|
|
45
|
+
/** The first argument `spec` does not accept, as an error sentence; null when every one is accepted. */
|
|
46
|
+
export function unknownArgument(args, spec) {
|
|
47
|
+
let positional = 0;
|
|
48
|
+
for (let i = 0; i < args.length; i++) {
|
|
49
|
+
const arg = args[i];
|
|
50
|
+
if (!arg.startsWith("-") || arg === "-") {
|
|
51
|
+
if (++positional > spec.positional)
|
|
52
|
+
return `unexpected argument ${arg}`;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const name = arg.split("=")[0];
|
|
56
|
+
if (spec.valued.includes(name)) {
|
|
57
|
+
// The value after a valued flag is not itself a flag, even when it starts with a dash.
|
|
58
|
+
if (!arg.includes("="))
|
|
59
|
+
i++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (spec.switches.includes(name) && !arg.includes("="))
|
|
63
|
+
continue;
|
|
64
|
+
const hint = spec.hints?.[name];
|
|
65
|
+
return `unknown option ${name}${hint ? ` — did you mean ${hint}?` : ""}`;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
/** Whether `command` names a subcommand. */
|
|
70
|
+
export function isSubcommand(command) {
|
|
71
|
+
return command !== undefined && Object.hasOwn(HANDLERS_OF, command);
|
|
72
|
+
}
|
|
73
|
+
/** Settle help and flag errors for a subcommand before its handler is reached. */
|
|
74
|
+
export function preflight(command, args) {
|
|
75
|
+
if (wantsHelp(args))
|
|
76
|
+
return { kind: "help" };
|
|
77
|
+
const spec = Object.hasOwn(HAND_PARSED, command) ? HAND_PARSED[command] : undefined;
|
|
78
|
+
const problem = spec ? unknownArgument(args, spec) : null;
|
|
79
|
+
return problem ? { kind: "error", message: problem } : { kind: "run" };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Run the command line: help and flag errors are settled here, so a subcommand's
|
|
83
|
+
* handler is reached only when it is actually meant to run.
|
|
84
|
+
*/
|
|
85
|
+
export async function dispatch(command, args, h) {
|
|
86
|
+
switch (command) {
|
|
87
|
+
// Asking for help is not an error; scripts and shells treat a non-zero exit as one.
|
|
88
|
+
case "--help":
|
|
89
|
+
case "-h":
|
|
90
|
+
case "help":
|
|
91
|
+
return h.usage(0);
|
|
92
|
+
case "--version":
|
|
93
|
+
case "-v":
|
|
94
|
+
return h.version();
|
|
95
|
+
}
|
|
96
|
+
if (!isSubcommand(command))
|
|
97
|
+
return h.usage(1);
|
|
98
|
+
const verdict = preflight(command, args);
|
|
99
|
+
if (verdict.kind === "help")
|
|
100
|
+
return h.usage(0);
|
|
101
|
+
if (verdict.kind === "error")
|
|
102
|
+
return h.refuse(verdict.message);
|
|
103
|
+
await h.commands[command](args);
|
|
104
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scenescout",
|
|
3
|
-
"version": "3.14.
|
|
3
|
+
"version": "3.14.1",
|
|
4
4
|
"description": "SceneScout — exploratory UI testing for AI coding agents. An MCP server that gives any agent (Claude Code, Cursor, VS Code Copilot, Codex, Gemini CLI and others) a structured view of a running web app, always-on oracles, a network-level write policy, memory across runs and a gap-checked report.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "brunoboto96",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"mcp-check": "npm run build && npm run mcp-check:run",
|
|
73
73
|
"mcp-check:run": "tsx scripts/mcp-check.ts",
|
|
74
74
|
"test": "npm run build && npm run test:unit && npm run smoke:run && npm run mcp-check:run",
|
|
75
|
-
"test:unit": "npm run scan-test && npm run oracle-test && npm run policy-test && npm run fixture-test && npm run dispatch-test && npm run design-test && npm run check-test && npm run ci-test && npm run qa-test && npm run contract-test && npm run pace-test && npm run limits-test && npm run request-test && npm run settle-test && npm run claims-test && npm run verify-test && npm run brief-test && npm run lane-test && npm run calibration-test && npm run bench-test && npm run memory-test && npm run install-test && npm run profiles-test && npm run refresh-test && npm run scripted-login-test && npm run live-test && npm run demo-test && npm run holdout-test && npm run hygiene-test",
|
|
75
|
+
"test:unit": "npm run scan-test && npm run oracle-test && npm run policy-test && npm run fixture-test && npm run dispatch-test && npm run design-test && npm run check-test && npm run ci-test && npm run qa-test && npm run contract-test && npm run pace-test && npm run limits-test && npm run request-test && npm run settle-test && npm run claims-test && npm run verify-test && npm run brief-test && npm run lane-test && npm run calibration-test && npm run bench-test && npm run memory-test && npm run install-test && npm run profiles-test && npm run refresh-test && npm run scripted-login-test && npm run live-test && npm run demo-test && npm run holdout-test && npm run hygiene-test && npm run guide-test",
|
|
76
76
|
"scan-test": "tsx scripts/scan-test.ts",
|
|
77
77
|
"oracle-test": "tsx --test scripts/oracle-test.ts",
|
|
78
78
|
"policy-test": "tsx --test scripts/policy-test.ts",
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"demo-test": "tsx --test scripts/demo-test.ts",
|
|
94
94
|
"holdout-test": "tsx --test scripts/holdout-test.ts",
|
|
95
95
|
"hygiene-test": "tsx --test scripts/hygiene-test.ts",
|
|
96
|
+
"guide-test": "tsx --test scripts/guide-test.ts",
|
|
96
97
|
"pace-test": "tsx --test scripts/pace-test.ts",
|
|
97
98
|
"limits-test": "tsx --test scripts/limits-test.ts",
|
|
98
99
|
"request-test": "tsx --test scripts/request-test.ts",
|