@mlola/browser-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +19 -0
- package/dist/args.d.ts +14 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +58 -0
- package/dist/args.js.map +1 -0
- package/dist/bin.d.ts +7 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +15 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/daemon.d.ts +6 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +58 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/doctor.d.ts +6 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +125 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/eval.d.ts +6 -0
- package/dist/commands/eval.d.ts.map +1 -0
- package/dist/commands/eval.js +51 -0
- package/dist/commands/eval.js.map +1 -0
- package/dist/commands/inspect.d.ts +9 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +178 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/run.d.ts +6 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +209 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/human-host.d.ts +24 -0
- package/dist/human-host.d.ts.map +1 -0
- package/dist/human-host.js +60 -0
- package/dist/human-host.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +5 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +133 -0
- package/dist/main.js.map +1 -0
- package/dist/ui.d.ts +30 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +106 -0
- package/dist/ui.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MLola
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @mlola/browser-cli
|
|
2
|
+
|
|
3
|
+
The `mlola browser` command line interface for the
|
|
4
|
+
[MLola Browser Runtime](https://mlola.com/browser-runtime).
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm install -g @mlola/browser-cli
|
|
8
|
+
|
|
9
|
+
mlola browser doctor
|
|
10
|
+
mlola browser run --goal "Download my latest invoice" --url https://billing.example.com/invoices
|
|
11
|
+
mlola browser sessions
|
|
12
|
+
mlola browser trace show <runId>
|
|
13
|
+
mlola browser eval --suite core
|
|
14
|
+
mlola browser daemon start
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
export TYPESAFE_API_KEY=… # fast path (https://typesafe.ai)
|
|
19
|
+
```
|
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal argument parser. No dependency on a CLI framework: the surface is
|
|
3
|
+
* small, and the runtime keeps its dependency graph tight on purpose.
|
|
4
|
+
*/
|
|
5
|
+
export interface ParsedArgs {
|
|
6
|
+
command: string[];
|
|
7
|
+
flags: Record<string, string | boolean>;
|
|
8
|
+
positional: string[];
|
|
9
|
+
}
|
|
10
|
+
export declare const parseArgs: (argv: string[]) => ParsedArgs;
|
|
11
|
+
export declare const flagString: (flags: Record<string, string | boolean>, key: string) => string | undefined;
|
|
12
|
+
export declare const flagBool: (flags: Record<string, string | boolean>, key: string) => boolean;
|
|
13
|
+
export declare const flagNumber: (flags: Record<string, string | boolean>, key: string) => number | undefined;
|
|
14
|
+
//# sourceMappingURL=args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IACxC,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,eAAO,MAAM,SAAS,GAAI,MAAM,MAAM,EAAE,KAAG,UAqC1C,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,KAAK,MAAM,KAAG,MAAM,GAAG,SAG1F,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,OAAO,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,KAAK,MAAM,KAAG,OACZ,CAAC;AAErE,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,KAAK,MAAM,KAAG,MAAM,GAAG,SAK1F,CAAC"}
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal argument parser. No dependency on a CLI framework: the surface is
|
|
3
|
+
* small, and the runtime keeps its dependency graph tight on purpose.
|
|
4
|
+
*/
|
|
5
|
+
export const parseArgs = (argv) => {
|
|
6
|
+
const flags = {};
|
|
7
|
+
const positional = [];
|
|
8
|
+
let i = 0;
|
|
9
|
+
while (i < argv.length) {
|
|
10
|
+
const arg = argv[i];
|
|
11
|
+
if (arg === "--") {
|
|
12
|
+
positional.push(...argv.slice(i + 1));
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
if (arg.startsWith("--")) {
|
|
16
|
+
const eq = arg.indexOf("=");
|
|
17
|
+
if (eq > 0) {
|
|
18
|
+
flags[arg.slice(2, eq)] = arg.slice(eq + 1);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
const key = arg.slice(2);
|
|
22
|
+
const next = argv[i + 1];
|
|
23
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
24
|
+
flags[key] = next;
|
|
25
|
+
i += 1;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
flags[key] = true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
i += 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (arg.startsWith("-") && arg.length > 1) {
|
|
35
|
+
const keys = arg.slice(1).split("");
|
|
36
|
+
for (const key of keys)
|
|
37
|
+
flags[key] = true;
|
|
38
|
+
i += 1;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
positional.push(arg);
|
|
42
|
+
i += 1;
|
|
43
|
+
}
|
|
44
|
+
return { command: [], flags, positional };
|
|
45
|
+
};
|
|
46
|
+
export const flagString = (flags, key) => {
|
|
47
|
+
const value = flags[key];
|
|
48
|
+
return typeof value === "string" ? value : undefined;
|
|
49
|
+
};
|
|
50
|
+
export const flagBool = (flags, key) => flags[key] === true || flags[key] === "true" || flags[key] === "1";
|
|
51
|
+
export const flagNumber = (flags, key) => {
|
|
52
|
+
const value = flagString(flags, key);
|
|
53
|
+
if (value === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
const parsed = Number(value);
|
|
56
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=args.js.map
|
package/dist/args.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;;GAGG"}
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `mlola` executable. The product surface is `mlola browser <command>`;
|
|
4
|
+
* `mlola <command>` is accepted as shorthand.
|
|
5
|
+
*/
|
|
6
|
+
import { run } from "./main.js";
|
|
7
|
+
run(process.argv.slice(2))
|
|
8
|
+
.then((code) => {
|
|
9
|
+
process.exitCode = code;
|
|
10
|
+
})
|
|
11
|
+
.catch((error) => {
|
|
12
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
});
|
|
15
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEhC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KACvB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;IACb,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC,CAAC;KACD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACpF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../src/commands/daemon.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAGzD,eAAO,MAAM,aAAa,GAAU,MAAM,UAAU,KAAG,OAAO,CAAC,MAAM,CAgDpE,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mlola browser daemon` — manage mlola-browserd (PRD §59).
|
|
3
|
+
*/
|
|
4
|
+
import { flagString } from "../args.js";
|
|
5
|
+
import { c, heading, kv, out } from "../ui.js";
|
|
6
|
+
export const daemonCommand = async (args) => {
|
|
7
|
+
const sub = args.positional[0] ?? "status";
|
|
8
|
+
const { startDaemon, stopDaemon, daemonStatus, readDaemonState } = await import("@mlola/browser-daemon");
|
|
9
|
+
const port = flagString(args.flags, "port");
|
|
10
|
+
switch (sub) {
|
|
11
|
+
case "start": {
|
|
12
|
+
const started = await startDaemon({
|
|
13
|
+
...(port ? { port: Number(port) } : {}),
|
|
14
|
+
...(args.flags["foreground"] === true ? { foreground: true } : {}),
|
|
15
|
+
});
|
|
16
|
+
heading("mlola-browserd");
|
|
17
|
+
kv("status", started.started ? c.green("started") : c.yellow("already running"));
|
|
18
|
+
kv("url", started.url);
|
|
19
|
+
kv("pid", String(started.pid));
|
|
20
|
+
kv("token", c.dim(started.tokenPath));
|
|
21
|
+
out("");
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
case "stop": {
|
|
25
|
+
const stopped = await stopDaemon();
|
|
26
|
+
heading("mlola-browserd");
|
|
27
|
+
kv("status", stopped.stopped ? c.green("stopped") : c.yellow("not running"));
|
|
28
|
+
if (stopped.forced)
|
|
29
|
+
kv("note", "sent SIGKILL after the graceful timeout");
|
|
30
|
+
out("");
|
|
31
|
+
return stopped.stopped ? 0 : 1;
|
|
32
|
+
}
|
|
33
|
+
case "status": {
|
|
34
|
+
const status = await daemonStatus();
|
|
35
|
+
const state = readDaemonState();
|
|
36
|
+
heading("mlola-browserd");
|
|
37
|
+
kv("status", status.running ? c.green("running") : c.dim("not running"));
|
|
38
|
+
if (state) {
|
|
39
|
+
kv("url", `http://127.0.0.1:${state.port}`);
|
|
40
|
+
kv("pid", String(state.pid));
|
|
41
|
+
kv("started", new Date(state.startedAt).toISOString());
|
|
42
|
+
kv("token", c.dim(state.tokenPath));
|
|
43
|
+
}
|
|
44
|
+
if (status.version)
|
|
45
|
+
kv("version", status.version);
|
|
46
|
+
if (status.protocol)
|
|
47
|
+
kv("protocol", status.protocol);
|
|
48
|
+
if (status.sessions !== undefined)
|
|
49
|
+
kv("sessions", String(status.sessions));
|
|
50
|
+
out("");
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
default:
|
|
54
|
+
out("usage: mlola browser daemon <start|stop|status> [--port N] [--foreground]");
|
|
55
|
+
return 2;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../src/commands/daemon.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAY,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAUvD,eAAO,MAAM,aAAa,GAAU,MAAM,UAAU,KAAG,OAAO,CAAC,MAAM,CAuHpE,CAAC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mlola browser doctor` — environment and readiness checks.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { resolveHome, daemonPaths } from "@mlola/browser-trace";
|
|
7
|
+
import { flagBool } from "../args.js";
|
|
8
|
+
import { c, heading, kv, out } from "../ui.js";
|
|
9
|
+
export const doctorCommand = async (args) => {
|
|
10
|
+
const json = flagBool(args.flags, "json");
|
|
11
|
+
const checks = [];
|
|
12
|
+
checks.push({
|
|
13
|
+
name: "node",
|
|
14
|
+
ok: Number(process.versions.node.split(".")[0]) >= 22,
|
|
15
|
+
detail: `v${process.versions.node}`,
|
|
16
|
+
fix: "upgrade to Node 22.18+ (24 recommended)",
|
|
17
|
+
});
|
|
18
|
+
// Fast path credentials.
|
|
19
|
+
const typesafeKey = process.env["TYPESAFE_API_KEY"];
|
|
20
|
+
checks.push({
|
|
21
|
+
name: "Jev fast path",
|
|
22
|
+
ok: typesafeKey ? true : "warn",
|
|
23
|
+
detail: typesafeKey
|
|
24
|
+
? `TYPESAFE_API_KEY is set (${typesafeKey.slice(0, 4)}…, ${typesafeKey.length} chars)`
|
|
25
|
+
: "TYPESAFE_API_KEY is not set — the fast path cannot run",
|
|
26
|
+
fix: "export TYPESAFE_API_KEY=… (https://typesafe.ai). Offline demos can use --provider keyword",
|
|
27
|
+
});
|
|
28
|
+
// Planner / text helper (optional).
|
|
29
|
+
const plannerKey = process.env["MLOLA_PLANNER_API_KEY"] ?? process.env["OPENAI_API_KEY"];
|
|
30
|
+
checks.push({
|
|
31
|
+
name: "planner (optional)",
|
|
32
|
+
ok: plannerKey ? true : "warn",
|
|
33
|
+
detail: plannerKey ? "an OpenAI-compatible planner key is set" : "no planner configured",
|
|
34
|
+
fix: "set MLOLA_PLANNER_API_KEY + MLOLA_PLANNER_BASE_URL + MLOLA_PLANNER_MODEL to enable planning and fallback",
|
|
35
|
+
});
|
|
36
|
+
// Playwright + Chrome.
|
|
37
|
+
try {
|
|
38
|
+
const playwright = await import("playwright-core");
|
|
39
|
+
checks.push({ name: "playwright-core", ok: true, detail: `loaded (chromium ${playwright.chromium.name()})` });
|
|
40
|
+
const channel = process.env["MLOLA_CHROME_CHANNEL"] ?? "chrome";
|
|
41
|
+
let launched = false;
|
|
42
|
+
let launchDetail = "";
|
|
43
|
+
try {
|
|
44
|
+
const browser = await playwright.chromium.launch({ channel, headless: true });
|
|
45
|
+
launched = true;
|
|
46
|
+
launchDetail = `launched ${channel} ${browser.version()}`;
|
|
47
|
+
await browser.close();
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
launchDetail = `${channel} launch failed: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`;
|
|
51
|
+
}
|
|
52
|
+
checks.push({
|
|
53
|
+
name: "browser",
|
|
54
|
+
ok: launched ? true : "warn",
|
|
55
|
+
detail: launched ? launchDetail : `${launchDetail} — falling back to bundled chromium`,
|
|
56
|
+
fix: "install Google Chrome, or run: npx playwright install chromium",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
checks.push({
|
|
61
|
+
name: "playwright-core",
|
|
62
|
+
ok: false,
|
|
63
|
+
detail: `not importable: ${String(error)}`,
|
|
64
|
+
fix: "npm install (the workspace declares playwright-core)",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Runtime home.
|
|
68
|
+
const home = resolveHome();
|
|
69
|
+
try {
|
|
70
|
+
mkdirSync(home, { recursive: true });
|
|
71
|
+
const probe = join(home, `.doctor-${process.pid}`);
|
|
72
|
+
writeFileSync(probe, "ok");
|
|
73
|
+
rmSync(probe, { force: true });
|
|
74
|
+
checks.push({ name: "runtime home", ok: true, detail: `${home} is writable` });
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
checks.push({
|
|
78
|
+
name: "runtime home",
|
|
79
|
+
ok: false,
|
|
80
|
+
detail: `${home} is not writable: ${String(error)}`,
|
|
81
|
+
fix: "set MLOLA_HOME to a writable directory",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// Daemon.
|
|
85
|
+
const paths = daemonPaths(home);
|
|
86
|
+
if (existsSync(paths.statePath)) {
|
|
87
|
+
try {
|
|
88
|
+
const state = JSON.parse(await import("node:fs/promises").then((fs) => fs.readFile(paths.statePath, "utf8")));
|
|
89
|
+
checks.push({
|
|
90
|
+
name: "daemon",
|
|
91
|
+
ok: "warn",
|
|
92
|
+
detail: `state file found (port ${state.port ?? "?"}, pid ${state.pid ?? "?"}) — verify with: mlola browser daemon status`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
checks.push({ name: "daemon", ok: "warn", detail: "state file exists but could not be read" });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
checks.push({
|
|
101
|
+
name: "daemon",
|
|
102
|
+
ok: "warn",
|
|
103
|
+
detail: "not running (optional; needed for background runs and extension sessions)",
|
|
104
|
+
fix: "mlola browser daemon start",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (json) {
|
|
108
|
+
out(JSON.stringify({ checks }, null, 2));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
heading("mlola browser doctor");
|
|
112
|
+
for (const check of checks) {
|
|
113
|
+
const mark = check.ok === true ? c.green("✔") : check.ok === "warn" ? c.yellow("!") : c.red("✖");
|
|
114
|
+
out(`${mark} ${c.bold(check.name.padEnd(20))} ${check.detail}`);
|
|
115
|
+
if (check.fix && check.ok !== true)
|
|
116
|
+
out(` ${c.dim(`fix: ${check.fix}`)}`);
|
|
117
|
+
}
|
|
118
|
+
out("");
|
|
119
|
+
const blocking = checks.filter((check) => check.ok === false);
|
|
120
|
+
kv("result", blocking.length === 0 ? c.green("ready") : c.red(`${blocking.length} blocking issue(s)`));
|
|
121
|
+
out("");
|
|
122
|
+
}
|
|
123
|
+
return checks.some((check) => check.ok === false) ? 1 : 0;
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAmB,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAS/C,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAAE,IAAgB,EAAmB,EAAE;IACvE,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAY,EAAE,CAAC;IAE3B,MAAM,CAAC,IAAI,CAAC;QACV,IAAI,EAAE,MAAM;QACZ,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;QACrD,MAAM,EAAE,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE;QACnC,GAAG,EAAE,yCAAyC;KAC/C,CAAC,CAAC;IAEH,yBAAyB;IACzB,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IACpD,MAAM,CAAC,IAAI,CAAC;QACV,IAAI,EAAE,eAAe;QACrB,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM;QAC/B,MAAM,EAAE,WAAW;YACjB,CAAC,CAAC,4BAA4B,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,WAAW,CAAC,MAAM,SAAS;YACtF,CAAC,CAAC,wDAAwD;QAC5D,GAAG,EAAE,2FAA2F;KACjG,CAAC,CAAC;IAEH,oCAAoC;IACpC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACzF,MAAM,CAAC,IAAI,CAAC;QACV,IAAI,EAAE,oBAAoB;QAC1B,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM;QAC9B,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,yCAAyC,CAAC,CAAC,CAAC,uBAAuB;QACxF,GAAG,EAAE,0GAA0G;KAChH,CAAC,CAAC;IAEH,uBAAuB;IACvB,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;QACnD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,oBAAoB,UAAU,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;QAC9G,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,IAAI,QAAQ,CAAC;QAChE,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,IAAI,YAAY,GAAG,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YAC9E,QAAQ,GAAG,IAAI,CAAC;YAChB,YAAY,GAAG,YAAY,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;YAC1D,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,YAAY,GAAG,GAAG,OAAO,mBAAmB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACtH,CAAC;QACD,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,SAAS;YACf,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM;YAC5B,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,YAAY,qCAAqC;YACtF,GAAG,EAAE,gEAAgE;SACtE,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,iBAAiB;YACvB,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,mBAAmB,MAAM,CAAC,KAAK,CAAC,EAAE;YAC1C,GAAG,EAAE,sDAAsD;SAC5D,CAAC,CAAC;IACL,CAAC;IAED,gBAAgB;IAChB,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACnD,aAAa,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC3B,MAAM,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,cAAc,EAAE,CAAC,CAAC;IACjF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,cAAc;YACpB,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,GAAG,IAAI,qBAAqB,MAAM,CAAC,KAAK,CAAC,EAAE;YACnD,GAAG,EAAE,wCAAwC;SAC9C,CAAC,CAAC;IACL,CAAC;IAED,UAAU;IACV,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAG3G,CAAC;YACF,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,QAAQ;gBACd,EAAE,EAAE,MAAM;gBACV,MAAM,EAAE,0BAA0B,KAAK,CAAC,IAAI,IAAI,GAAG,SAAS,KAAK,CAAC,GAAG,IAAI,GAAG,8CAA8C;aAC3H,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,yCAAyC,EAAE,CAAC,CAAC;QACjG,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,QAAQ;YACd,EAAE,EAAE,MAAM;YACV,MAAM,EAAE,2EAA2E;YACnF,GAAG,EAAE,4BAA4B;SAClC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,IAAI,EAAE,CAAC;QACT,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3C,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,sBAAsB,CAAC,CAAC;QAChC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACjG,GAAG,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;YAChE,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,EAAE,KAAK,IAAI;gBAAE,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,QAAQ,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC7E,CAAC;QACD,GAAG,CAAC,EAAE,CAAC,CAAC;QACR,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC;QAC9D,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,MAAM,oBAAoB,CAAC,CAAC,CAAC;QACvG,GAAG,CAAC,EAAE,CAAC,CAAC;IACV,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5D,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval.d.ts","sourceRoot":"","sources":["../../src/commands/eval.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAGnE,eAAO,MAAM,WAAW,GAAU,MAAM,UAAU,KAAG,OAAO,CAAC,MAAM,CAsDlE,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mlola browser eval` — deterministic eval suites (PRD §49, §50).
|
|
3
|
+
*/
|
|
4
|
+
import { flagBool, flagString } from "../args.js";
|
|
5
|
+
import { c, heading, kv, out } from "../ui.js";
|
|
6
|
+
export const evalCommand = async (args) => {
|
|
7
|
+
const suite = (flagString(args.flags, "suite") ?? args.positional[0] ?? "core");
|
|
8
|
+
const filter = flagString(args.flags, "filter");
|
|
9
|
+
const provider = (flagString(args.flags, "provider") ?? "scripted");
|
|
10
|
+
const json = flagBool(args.flags, "json");
|
|
11
|
+
const headless = flagBool(args.flags, "headless") || true;
|
|
12
|
+
const { runSuites } = await import("@mlola/browser-evals");
|
|
13
|
+
if (!json) {
|
|
14
|
+
heading(`mlola browser eval ${c.dim(`${suite} · ${provider}`)}`);
|
|
15
|
+
kv("provider", provider === "typesafe" ? "TypeSafe Jev (live)" : `${provider} (offline, deterministic)`);
|
|
16
|
+
if (filter)
|
|
17
|
+
kv("filter", filter);
|
|
18
|
+
out("");
|
|
19
|
+
}
|
|
20
|
+
const report = await runSuites({
|
|
21
|
+
suite,
|
|
22
|
+
...(filter ? { filter } : {}),
|
|
23
|
+
provider,
|
|
24
|
+
headless,
|
|
25
|
+
onProgress: json ? undefined : (line) => out(` ${line}`),
|
|
26
|
+
});
|
|
27
|
+
if (json) {
|
|
28
|
+
out(JSON.stringify(report, null, 2));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
out("");
|
|
32
|
+
const mark = report.failed === 0 ? c.green("PASS") : c.red("FAIL");
|
|
33
|
+
kv("result", `${mark} ${report.passed} passed, ${report.failed} failed, ${report.blocked} blocked, ${report.unverified} unverified (${report.cases.length} cases)`);
|
|
34
|
+
kv("duration", `${(report.durationMs / 1000).toFixed(1)}s`);
|
|
35
|
+
if (report.failed > 0) {
|
|
36
|
+
out("");
|
|
37
|
+
out(c.red(" failures"));
|
|
38
|
+
for (const failure of report.cases.filter((x) => x.status === "failed")) {
|
|
39
|
+
out(` ${failure.name}: ${failure.detail}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (report.fastPathShare !== undefined) {
|
|
43
|
+
kv("fast path", `${Math.round(report.fastPathShare * 100)}% of steps`);
|
|
44
|
+
}
|
|
45
|
+
for (const line of report.notes ?? [])
|
|
46
|
+
out(c.dim(` note: ${line}`));
|
|
47
|
+
out("");
|
|
48
|
+
}
|
|
49
|
+
return report.failed === 0 ? 0 : 1;
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=eval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval.js","sourceRoot":"","sources":["../../src/commands/eval.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mlola browser trace` / `mlola browser sessions` — inspect local runs.
|
|
3
|
+
*
|
|
4
|
+
* The event log is the source of truth: these commands only read it.
|
|
5
|
+
*/
|
|
6
|
+
import { type ParsedArgs } from "../args.ts";
|
|
7
|
+
export declare const sessionsCommand: (args: ParsedArgs) => Promise<number>;
|
|
8
|
+
export declare const traceCommand: (args: ParsedArgs) => Promise<number>;
|
|
9
|
+
//# sourceMappingURL=inspect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../src/commands/inspect.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAoC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAY/E,eAAO,MAAM,eAAe,GAAU,MAAM,UAAU,KAAG,OAAO,CAAC,MAAM,CAoEtE,CAAC;AAEF,eAAO,MAAM,YAAY,GAAU,MAAM,UAAU,KAAG,OAAO,CAAC,MAAM,CAqCnE,CAAC"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mlola browser trace` / `mlola browser sessions` — inspect local runs.
|
|
3
|
+
*
|
|
4
|
+
* The event log is the source of truth: these commands only read it.
|
|
5
|
+
*/
|
|
6
|
+
import { flagBool, flagNumber, flagString } from "../args.js";
|
|
7
|
+
import { c, formatAge, formatDuration, heading, kv, out, table } from "../ui.js";
|
|
8
|
+
import { computeMetrics, listRuns, readRunEvents, readRunMeta, readRunSummary, resolveHome, } from "@mlola/browser-trace";
|
|
9
|
+
export const sessionsCommand = async (args) => {
|
|
10
|
+
const sub = args.positional[0] ?? "list";
|
|
11
|
+
const json = flagBool(args.flags, "json");
|
|
12
|
+
if (sub === "list" || sub === "ls") {
|
|
13
|
+
const runs = listRuns(resolveHome(), flagNumber(args.flags, "limit") ?? 25);
|
|
14
|
+
if (json) {
|
|
15
|
+
out(JSON.stringify(runs, null, 2));
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
heading("recent runs");
|
|
19
|
+
if (runs.length === 0) {
|
|
20
|
+
out(c.dim(" (no runs yet)"));
|
|
21
|
+
out("");
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
table(runs.map((run) => [
|
|
25
|
+
run.runId,
|
|
26
|
+
statusColor(run.status),
|
|
27
|
+
String(run.steps ?? "?"),
|
|
28
|
+
formatAge(run.modifiedAt),
|
|
29
|
+
truncate(run.goal, 60),
|
|
30
|
+
]), ["run", "status", "steps", "when", "goal"]);
|
|
31
|
+
out("");
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
const runId = args.positional[1] ?? flagString(args.flags, "run");
|
|
35
|
+
if (!runId) {
|
|
36
|
+
out("usage: mlola browser sessions show <runId>");
|
|
37
|
+
return 2;
|
|
38
|
+
}
|
|
39
|
+
const meta = readRunMeta(runId, resolveHome());
|
|
40
|
+
const summary = readRunSummary(runId, resolveHome());
|
|
41
|
+
const events = readRunEvents(runId, resolveHome());
|
|
42
|
+
const metrics = computeMetrics(events);
|
|
43
|
+
if (json) {
|
|
44
|
+
out(JSON.stringify({ meta, summary, metrics }, null, 2));
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
heading(`run ${runId}`);
|
|
48
|
+
if (meta) {
|
|
49
|
+
kv("goal", meta.goal);
|
|
50
|
+
kv("backend", meta.backend);
|
|
51
|
+
kv("started", new Date(meta.startedAt).toISOString());
|
|
52
|
+
}
|
|
53
|
+
if (summary) {
|
|
54
|
+
kv("status", statusColor(summary.status));
|
|
55
|
+
kv("reason", summary.reason);
|
|
56
|
+
kv("steps", `${summary.steps} in ${formatDuration(summary.durationMs)}`);
|
|
57
|
+
if (summary.artifacts.length > 0) {
|
|
58
|
+
kv("artifacts", summary.artifacts.map((a) => `${a.id}:${a.name}`).join(", "));
|
|
59
|
+
}
|
|
60
|
+
if (summary.verification) {
|
|
61
|
+
kv("verification", `${summary.verification.status} — ${summary.verification.evidence[0]?.detail ?? ""}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
out("");
|
|
65
|
+
out(c.bold(" metrics"));
|
|
66
|
+
for (const line of formatMetricsLines(metrics))
|
|
67
|
+
out(` ${line}`);
|
|
68
|
+
out("");
|
|
69
|
+
return 0;
|
|
70
|
+
};
|
|
71
|
+
export const traceCommand = async (args) => {
|
|
72
|
+
const sub = args.positional[0] ?? "show";
|
|
73
|
+
const runId = args.positional[1] ?? flagString(args.flags, "run") ?? latestRunId();
|
|
74
|
+
if (!runId) {
|
|
75
|
+
out("usage: mlola browser trace show <runId> [--type EVENT] [--limit N]");
|
|
76
|
+
return 2;
|
|
77
|
+
}
|
|
78
|
+
const events = readRunEvents(runId, resolveHome());
|
|
79
|
+
if (events.length === 0) {
|
|
80
|
+
out(c.dim(`no events found for run ${runId}`));
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
if (sub === "show" || sub === "list") {
|
|
84
|
+
const typeFilter = flagString(args.flags, "type");
|
|
85
|
+
const limit = flagNumber(args.flags, "limit") ?? 40;
|
|
86
|
+
const json = flagBool(args.flags, "json");
|
|
87
|
+
const filtered = (typeFilter ? events.filter((e) => e.type.includes(typeFilter.toUpperCase())) : events).slice(-limit);
|
|
88
|
+
if (json) {
|
|
89
|
+
out(JSON.stringify(filtered, null, 2));
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
heading(`trace ${runId}`);
|
|
93
|
+
for (const event of filtered)
|
|
94
|
+
out(formatEvent(event));
|
|
95
|
+
out("");
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
if (sub === "summary") {
|
|
99
|
+
const metrics = computeMetrics(events);
|
|
100
|
+
out(JSON.stringify({ runId, metrics }, null, 2));
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
out(`unknown trace subcommand: ${sub}`);
|
|
104
|
+
return 2;
|
|
105
|
+
};
|
|
106
|
+
const formatEvent = (event) => {
|
|
107
|
+
const when = new Date(event.ts).toISOString().slice(11, 23);
|
|
108
|
+
const seq = String(event.seq).padStart(4, " ");
|
|
109
|
+
const detail = describeEvent(event);
|
|
110
|
+
const type = event.type.padEnd(22);
|
|
111
|
+
return `${c.dim(`${seq} ${when}`)} ${typeColor(event.type, type)} ${detail}`;
|
|
112
|
+
};
|
|
113
|
+
const describeEvent = (event) => {
|
|
114
|
+
switch (event.type) {
|
|
115
|
+
case "RUN_CREATED":
|
|
116
|
+
return `goal: ${event.payload.goal}`;
|
|
117
|
+
case "SNAPSHOT_CAPTURED":
|
|
118
|
+
return `${event.payload.nodeCount} nodes · ${Math.round(event.payload.latencyMs)}ms · ${truncate(event.payload.url, 60)}`;
|
|
119
|
+
case "DECISION_RECEIVED":
|
|
120
|
+
return `${event.payload.decision.operation}${event.payload.decision.target ? ` → ${event.payload.decision.target}` : ""} conf=${event.payload.decision.operationConfidence.toFixed(2)} ${Math.round(event.payload.decision.latencyMs)}ms`;
|
|
121
|
+
case "DECISION_INVALID":
|
|
122
|
+
return truncate(event.payload.reason, 100);
|
|
123
|
+
case "ACTION_EXECUTED":
|
|
124
|
+
return `${event.payload.action} ${Math.round(event.payload.latencyMs)}ms${event.payload.detail ? ` — ${truncate(event.payload.detail, 60)}` : ""}`;
|
|
125
|
+
case "ACTION_FAILED":
|
|
126
|
+
return `${event.payload.action}: ${event.payload.failure.code} ${truncate(event.payload.failure.message, 60)}`;
|
|
127
|
+
case "POLICY_ALLOWED":
|
|
128
|
+
case "POLICY_DENIED":
|
|
129
|
+
return `${event.payload.outcome.mode} (${event.payload.outcome.risk}) ${truncate(event.payload.summary, 60)}`;
|
|
130
|
+
case "RECOVERY_STARTED":
|
|
131
|
+
return `${event.payload.strategy} for ${event.payload.failure.code}`;
|
|
132
|
+
case "VERIFICATION_RESULT":
|
|
133
|
+
return `${event.payload.verification.status}: ${truncate(event.payload.verification.evidence[0]?.detail ?? "", 80)}`;
|
|
134
|
+
case "DOWNLOAD_COMPLETED":
|
|
135
|
+
return `${event.payload.artifact.name} (${event.payload.artifact.documentType ?? event.payload.artifact.mimeType})`;
|
|
136
|
+
case "UPLOAD_COMPLETED":
|
|
137
|
+
return `${event.payload.artifactId}: ${truncate(event.payload.evidence, 60)}`;
|
|
138
|
+
case "RUN_COMPLETED":
|
|
139
|
+
return c.green("completed");
|
|
140
|
+
case "RUN_FAILED":
|
|
141
|
+
return c.red(`${event.payload.failure.code}: ${truncate(event.payload.failure.message, 60)}`);
|
|
142
|
+
case "NOTE":
|
|
143
|
+
return truncate(event.payload.text, 90);
|
|
144
|
+
default:
|
|
145
|
+
return "";
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
const typeColor = (type, text) => {
|
|
149
|
+
if (type.startsWith("RUN_FAILED") || type.includes("FAILED") || type.includes("DENIED"))
|
|
150
|
+
return c.red(text);
|
|
151
|
+
if (type.includes("RECOVERY") || type.includes("WARNING") || type.includes("LOOP"))
|
|
152
|
+
return c.yellow(text);
|
|
153
|
+
if (type.includes("COMPLETED") || type.includes("VERIFIED"))
|
|
154
|
+
return c.green(text);
|
|
155
|
+
if (type.includes("DECISION"))
|
|
156
|
+
return c.cyan(text);
|
|
157
|
+
return text;
|
|
158
|
+
};
|
|
159
|
+
const statusColor = (status) => {
|
|
160
|
+
if (status === "completed")
|
|
161
|
+
return c.green(status);
|
|
162
|
+
if (status === "failed")
|
|
163
|
+
return c.red(status);
|
|
164
|
+
if (status === "running")
|
|
165
|
+
return c.cyan(status);
|
|
166
|
+
return c.dim(status);
|
|
167
|
+
};
|
|
168
|
+
const formatMetricsLines = (metrics) => [
|
|
169
|
+
`steps ${metrics.steps} · snapshots ${metrics.snapshots} · decision calls ${metrics.decisionCalls}`,
|
|
170
|
+
`decision latency p50 ${Math.round(metrics.decisionLatencyMs.p50)}ms p95 ${Math.round(metrics.decisionLatencyMs.p95)}ms`,
|
|
171
|
+
`snapshot p50 ${Math.round(metrics.snapshotLatencyMs.p50)}ms · action p50 ${Math.round(metrics.browserLatencyMs.p50)}ms`,
|
|
172
|
+
`fast-path share ${Math.round(metrics.fastPathShare * 100)}% · fallbacks ${metrics.fallbacks}`,
|
|
173
|
+
`recoveries ${metrics.recoveries} (ok ${metrics.recoveriesSucceeded}) · loops ${metrics.loopDetections} · policy denials ${metrics.policyDenials}`,
|
|
174
|
+
`artifacts ${metrics.artifacts} · downloads ${metrics.downloads} · uploads ${metrics.uploads} · cost $${metrics.costUsd.toFixed(4)}`,
|
|
175
|
+
];
|
|
176
|
+
const latestRunId = () => listRuns(resolveHome(), 1)[0]?.runId;
|
|
177
|
+
const truncate = (s, n) => (s.length <= n ? s : `${s.slice(0, n - 1)}…`);
|
|
178
|
+
//# sourceMappingURL=inspect.js.map
|