@iamu-dev/iamu 0.8.0-beta.0 → 0.8.0-beta.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/dist/bin.js +878 -227
- package/dist/commands/browse-actions.d.ts +1 -0
- package/dist/commands/browse-driver.d.ts +25 -5
- package/dist/commands/browse.d.ts +21 -1
- package/dist/commands/edit-entity.d.ts +3 -3
- package/dist/commands/init.d.ts +4 -4
- package/dist/commands/instructions-walk.d.ts +6 -0
- package/dist/commands/preflight.d.ts +4 -2
- package/dist/commands/step.d.ts +2 -0
- package/dist/index.js +878 -227
- package/dist/templates/prompts/walk.md +7 -5
- package/dist/templates/skills/iamu-design.md +20 -15
- package/dist/templates/skills/iamu-run.md +40 -16
- package/dist/templates/skills/iamu-setup.md +154 -75
- package/dist/templates/skills/iamu-stage.md +29 -23
- package/dist/templates/skills/iamu-status.md +2 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -2534,8 +2534,39 @@ var init_dist = __esm({
|
|
|
2534
2534
|
|
|
2535
2535
|
// ../../packages/local-store/dist/atomic.js
|
|
2536
2536
|
import { randomUUID } from "crypto";
|
|
2537
|
-
import { closeSync, fsyncSync, mkdirSync, openSync, promises as fs,
|
|
2537
|
+
import fsSync, { closeSync, fsyncSync, mkdirSync, openSync, promises as fs, unlinkSync, writeFileSync } from "fs";
|
|
2538
2538
|
import path from "path";
|
|
2539
|
+
function isTransientWindowsRenameError(err) {
|
|
2540
|
+
const code = err?.code;
|
|
2541
|
+
return code === "EPERM" || code === "EACCES";
|
|
2542
|
+
}
|
|
2543
|
+
function renameSyncWithRetry(src, dest, maxRetries = MAX_RENAME_RETRIES) {
|
|
2544
|
+
for (let attempt = 0; ; attempt++) {
|
|
2545
|
+
try {
|
|
2546
|
+
fsSync.renameSync(src, dest);
|
|
2547
|
+
return;
|
|
2548
|
+
} catch (err) {
|
|
2549
|
+
if (isTransientWindowsRenameError(err) && attempt < maxRetries) {
|
|
2550
|
+
continue;
|
|
2551
|
+
}
|
|
2552
|
+
throw err;
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
async function renameWithRetry(src, dest, maxRetries = MAX_RENAME_RETRIES) {
|
|
2557
|
+
for (let attempt = 0; ; attempt++) {
|
|
2558
|
+
try {
|
|
2559
|
+
await fs.rename(src, dest);
|
|
2560
|
+
return;
|
|
2561
|
+
} catch (err) {
|
|
2562
|
+
if (isTransientWindowsRenameError(err) && attempt < maxRetries) {
|
|
2563
|
+
await new Promise((resolve3) => setTimeout(resolve3, 15));
|
|
2564
|
+
continue;
|
|
2565
|
+
}
|
|
2566
|
+
throw err;
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2539
2570
|
function atomicWriteFileSync(targetPath, content, opts = {}) {
|
|
2540
2571
|
const dir = path.dirname(targetPath);
|
|
2541
2572
|
const base = path.basename(targetPath);
|
|
@@ -2549,7 +2580,7 @@ function atomicWriteFileSync(targetPath, content, opts = {}) {
|
|
|
2549
2580
|
fsyncSync(fd);
|
|
2550
2581
|
closeSync(fd);
|
|
2551
2582
|
fd = null;
|
|
2552
|
-
|
|
2583
|
+
renameSyncWithRetry(tmpPath, targetPath);
|
|
2553
2584
|
renamed2 = true;
|
|
2554
2585
|
} finally {
|
|
2555
2586
|
if (fd !== null) {
|
|
@@ -2581,7 +2612,7 @@ async function atomicWriteFile(targetPath, content, opts = {}) {
|
|
|
2581
2612
|
} finally {
|
|
2582
2613
|
await handle.close();
|
|
2583
2614
|
}
|
|
2584
|
-
await
|
|
2615
|
+
await renameWithRetry(tmpPath, targetPath);
|
|
2585
2616
|
renamed2 = true;
|
|
2586
2617
|
} finally {
|
|
2587
2618
|
if (!renamed2) {
|
|
@@ -2603,9 +2634,11 @@ async function appendJsonLine(targetPath, record) {
|
|
|
2603
2634
|
await handle.close();
|
|
2604
2635
|
}
|
|
2605
2636
|
}
|
|
2637
|
+
var MAX_RENAME_RETRIES;
|
|
2606
2638
|
var init_atomic = __esm({
|
|
2607
2639
|
"../../packages/local-store/dist/atomic.js"() {
|
|
2608
2640
|
"use strict";
|
|
2641
|
+
MAX_RENAME_RETRIES = 4;
|
|
2609
2642
|
}
|
|
2610
2643
|
});
|
|
2611
2644
|
|
|
@@ -10753,6 +10786,7 @@ async function readDesigner(projectRoot, runId, findings2) {
|
|
|
10753
10786
|
design_files: raw.design_files ?? [],
|
|
10754
10787
|
summary: raw.summary ?? "",
|
|
10755
10788
|
...raw.skipped ? { skipped: raw.skipped.reason ?? "skipped" } : {},
|
|
10789
|
+
...raw.screens && raw.screens.length > 0 ? { screens: raw.screens } : {},
|
|
10756
10790
|
rules_broken: broken,
|
|
10757
10791
|
rules_held: held,
|
|
10758
10792
|
issues: (raw.issues ?? []).map((i) => ({
|
|
@@ -10821,7 +10855,7 @@ async function buildCanonical(projectRoot, manifest, completedAt, inputs) {
|
|
|
10821
10855
|
// Step files record the path relative to the run directory
|
|
10822
10856
|
// (`screenshots/step-1.png`); older ones recorded the bare file
|
|
10823
10857
|
// name. Normalize on the file name so the prefix is never doubled.
|
|
10824
|
-
screenshot_ref: s.screenshot ? path20.join("screenshots", path20.basename(s.screenshot)) : null,
|
|
10858
|
+
screenshot_ref: s.screenshot ? path20.posix.join("screenshots", path20.basename(s.screenshot)) : null,
|
|
10825
10859
|
persona_reasoning: s.note,
|
|
10826
10860
|
grounding: s.grounding ?? null,
|
|
10827
10861
|
action: s.action,
|
|
@@ -15224,6 +15258,13 @@ function renderDesigner(designer2, lines) {
|
|
|
15224
15258
|
}
|
|
15225
15259
|
lines.push("", designerRuleCounts(designer2));
|
|
15226
15260
|
}
|
|
15261
|
+
if (designer2.proposals.length > 0) {
|
|
15262
|
+
lines.push("", `Proposed doc edits: ${designer2.proposals.map((p) => `\`${p.slug}\` (${p.outcome})`).join(", ")}. Decide them with \`iamu design proposals\`.`);
|
|
15263
|
+
}
|
|
15264
|
+
}
|
|
15265
|
+
function renderDesignerFindings(designer2, lines) {
|
|
15266
|
+
if (designer2.skipped)
|
|
15267
|
+
return;
|
|
15227
15268
|
const own = designer2.issues.filter((i) => !i.design_cause_of);
|
|
15228
15269
|
lines.push("", own.length === 0 ? "_No designer findings beyond the design causes above._" : `${designerCount(designer2)}, worst first.`);
|
|
15229
15270
|
for (const g of designerIssuesByScreen(designer2)) {
|
|
@@ -15237,9 +15278,35 @@ function renderDesigner(designer2, lines) {
|
|
|
15237
15278
|
lines.push(`- ${i.id} **${i.title}** (${designerIssueMeta(i, (t) => `_${t}_`)}).${text.length > 0 ? ` ${text.join(" ")}` : ""}`);
|
|
15238
15279
|
}
|
|
15239
15280
|
}
|
|
15240
|
-
|
|
15241
|
-
|
|
15281
|
+
}
|
|
15282
|
+
function designerScreenReadingsByStep(designer2, steps2) {
|
|
15283
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
15284
|
+
if (!designer2?.screens || designer2.screens.length === 0)
|
|
15285
|
+
return out2;
|
|
15286
|
+
const byKey = new Map(designer2.screens.filter((s) => s.reading?.trim() || s.serves && s.serves.length > 0).map((s) => [s.key, s]));
|
|
15287
|
+
const shown = /* @__PURE__ */ new Set();
|
|
15288
|
+
for (const st of steps2) {
|
|
15289
|
+
const candidates = [
|
|
15290
|
+
pagePath(st.url),
|
|
15291
|
+
st.surface ?? null,
|
|
15292
|
+
`step-${st.step_number}`
|
|
15293
|
+
].filter((c) => Boolean(c));
|
|
15294
|
+
for (const key of candidates) {
|
|
15295
|
+
if (shown.has(key))
|
|
15296
|
+
continue;
|
|
15297
|
+
const screen = byKey.get(key);
|
|
15298
|
+
if (screen) {
|
|
15299
|
+
shown.add(key);
|
|
15300
|
+
out2.set(st.step_number, {
|
|
15301
|
+
key: screen.key,
|
|
15302
|
+
reading: screen.reading.trim(),
|
|
15303
|
+
serves: (screen.serves ?? []).map((x) => x.what.trim()).filter(Boolean)
|
|
15304
|
+
});
|
|
15305
|
+
break;
|
|
15306
|
+
}
|
|
15307
|
+
}
|
|
15242
15308
|
}
|
|
15309
|
+
return out2;
|
|
15243
15310
|
}
|
|
15244
15311
|
function renderRunDetails(c, lines) {
|
|
15245
15312
|
lines.push("", "## Run details", "", `- Run ID: ${c.run_id}`, `- Target URL: ${c.target.url}`);
|
|
@@ -15277,12 +15344,13 @@ function renderRunDetails(c, lines) {
|
|
|
15277
15344
|
if (diff)
|
|
15278
15345
|
lines.push("", renderJourneyDiffMarkdown(diff));
|
|
15279
15346
|
}
|
|
15280
|
-
function renderSteps(c, lines) {
|
|
15347
|
+
function renderSteps(c, designer2, lines) {
|
|
15281
15348
|
lines.push("", "## Steps");
|
|
15282
15349
|
if (c.steps.length === 0) {
|
|
15283
15350
|
lines.push("", "_No steps recorded._");
|
|
15284
15351
|
return;
|
|
15285
15352
|
}
|
|
15353
|
+
const readings = designerScreenReadingsByStep(designer2, c.steps);
|
|
15286
15354
|
for (const s of c.steps) {
|
|
15287
15355
|
lines.push("", `### ${s.step_number}. ${s.screen_title ?? "(untitled)"} \u2014 ${s.id}`);
|
|
15288
15356
|
const where = [s.surface, s.url].filter(Boolean).join(" \u2014 ");
|
|
@@ -15290,6 +15358,16 @@ function renderSteps(c, lines) {
|
|
|
15290
15358
|
lines.push("", `Page: ${where}`);
|
|
15291
15359
|
if (s.persona_reasoning)
|
|
15292
15360
|
lines.push("", `> ${s.persona_reasoning.replace(/\n/g, "\n> ")}`);
|
|
15361
|
+
const reading = readings.get(s.step_number);
|
|
15362
|
+
if (reading) {
|
|
15363
|
+
const parts = [
|
|
15364
|
+
reading.reading,
|
|
15365
|
+
reading.serves.length > 0 ? `Serves: ${reading.serves.join("; ")}` : ""
|
|
15366
|
+
].filter(Boolean);
|
|
15367
|
+
if (parts.length > 0) {
|
|
15368
|
+
lines.push("", `**Designer reading** (\`${reading.key}\`): ${parts.join(" ")}`);
|
|
15369
|
+
}
|
|
15370
|
+
}
|
|
15293
15371
|
if (s.grounding)
|
|
15294
15372
|
lines.push("", `Grounding: ${s.grounding}`);
|
|
15295
15373
|
if (s.screenshot_ref)
|
|
@@ -15327,7 +15405,10 @@ function renderCanonicalMarkdown(canonical3) {
|
|
|
15327
15405
|
const checks = c.checks;
|
|
15328
15406
|
if (checks)
|
|
15329
15407
|
renderChecks(c, checks, lines);
|
|
15408
|
+
const designer2 = canonicalDesignerOf(c);
|
|
15330
15409
|
renderHumanTest(c, lines);
|
|
15410
|
+
if (designer2)
|
|
15411
|
+
renderDesigner(designer2, lines);
|
|
15331
15412
|
const verification = c.verification;
|
|
15332
15413
|
const newIds = new Set(verification?.new_findings.map((n) => n.id) ?? []);
|
|
15333
15414
|
const problems = c.findings.filter((f) => f.type !== "helps" && !isSetupArtifact(f)).sort((a, b) => rank(a.severity, SEVERITY_ORDER) - rank(b.severity, SEVERITY_ORDER) || rank(a.type, TYPE_ORDER) - rank(b.type, TYPE_ORDER));
|
|
@@ -15335,7 +15416,6 @@ function renderCanonicalMarkdown(canonical3) {
|
|
|
15335
15416
|
if (verification) {
|
|
15336
15417
|
renderSince(verification, problems.filter((f) => newIds.has(f.id)).length, strengths.filter((f) => newIds.has(f.id)).length, lines);
|
|
15337
15418
|
}
|
|
15338
|
-
const designer2 = canonicalDesignerOf(c);
|
|
15339
15419
|
const causes = designCausesByFinding(designer2);
|
|
15340
15420
|
lines.push("", "## Findings");
|
|
15341
15421
|
if (problems.length === 0) {
|
|
@@ -15352,11 +15432,11 @@ function renderCanonicalMarkdown(canonical3) {
|
|
|
15352
15432
|
renderFinding(c, f, i + 1, since, causes.get(f.id) ?? [], lines);
|
|
15353
15433
|
});
|
|
15354
15434
|
}
|
|
15435
|
+
if (designer2)
|
|
15436
|
+
renderDesignerFindings(designer2, lines);
|
|
15355
15437
|
const analysis = c.analysis;
|
|
15356
15438
|
if (analysis)
|
|
15357
15439
|
renderDirections(analysis, lines);
|
|
15358
|
-
if (designer2)
|
|
15359
|
-
renderDesigner(designer2, lines);
|
|
15360
15440
|
if (analysis)
|
|
15361
15441
|
renderNotes(analysis, lines);
|
|
15362
15442
|
const what = c.what_worked ?? [];
|
|
@@ -15380,7 +15460,7 @@ function renderCanonicalMarkdown(canonical3) {
|
|
|
15380
15460
|
for (const n of setup)
|
|
15381
15461
|
lines.push(`- ${n}`);
|
|
15382
15462
|
}
|
|
15383
|
-
renderSteps(c, lines);
|
|
15463
|
+
renderSteps(c, designer2, lines);
|
|
15384
15464
|
renderRunDetails(c, lines);
|
|
15385
15465
|
return `${lines.join("\n")}
|
|
15386
15466
|
`;
|
|
@@ -22704,8 +22784,8 @@ import { dirname as dirname3, join as join5, resolve as resolve2 } from "path";
|
|
|
22704
22784
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
22705
22785
|
import chalk6 from "chalk";
|
|
22706
22786
|
function getVersion() {
|
|
22707
|
-
if ("0.8.0-beta.
|
|
22708
|
-
return "0.8.0-beta.
|
|
22787
|
+
if ("0.8.0-beta.1") {
|
|
22788
|
+
return "0.8.0-beta.1";
|
|
22709
22789
|
}
|
|
22710
22790
|
try {
|
|
22711
22791
|
const thisDir = dirname3(fileURLToPath2(import.meta.url));
|
|
@@ -23649,7 +23729,7 @@ import chalk9 from "chalk";
|
|
|
23649
23729
|
function openBrowser(url, run = spawnDetached) {
|
|
23650
23730
|
try {
|
|
23651
23731
|
const parsed = new URL(url);
|
|
23652
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
23732
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:" && parsed.protocol !== "file:") {
|
|
23653
23733
|
return;
|
|
23654
23734
|
}
|
|
23655
23735
|
} catch {
|
|
@@ -24450,6 +24530,7 @@ function renderRunView(v, width = 100) {
|
|
|
24450
24530
|
` ${verdictColor(t.label)(padRight(t.label, 8))} ${truncate(t.question, w - 14)}`
|
|
24451
24531
|
);
|
|
24452
24532
|
}
|
|
24533
|
+
if (v.designer) lines.push(...renderDesignerBlock(v.designer, w));
|
|
24453
24534
|
if (v.since) {
|
|
24454
24535
|
const s = v.since;
|
|
24455
24536
|
const parts = [
|
|
@@ -24538,6 +24619,9 @@ function renderRunView(v, width = 100) {
|
|
|
24538
24619
|
` ${padRight(chalk12.green("\u2713"), 3)}${padRight("", 8)} ${padRight(chalk12.green("helps"), 9)} ${truncate(s, titleWidth + whereWidth)}`
|
|
24539
24620
|
);
|
|
24540
24621
|
}
|
|
24622
|
+
if (v.designer && !v.designer.skipped) {
|
|
24623
|
+
lines.push(...renderDesignerFindingsBlock(v.designer, w));
|
|
24624
|
+
}
|
|
24541
24625
|
if (v.directions.length > 0) {
|
|
24542
24626
|
lines.push("", ` ${chalk12.bold("Directions")}`);
|
|
24543
24627
|
for (const d of v.directions) {
|
|
@@ -24549,7 +24633,6 @@ function renderRunView(v, width = 100) {
|
|
|
24549
24633
|
);
|
|
24550
24634
|
}
|
|
24551
24635
|
}
|
|
24552
|
-
if (v.designer) lines.push(...renderDesignerBlock(v.designer, w));
|
|
24553
24636
|
if (v.observations.length > 0 || v.setupNotes.length > 0) {
|
|
24554
24637
|
lines.push("", ` ${chalk12.bold("Also worth knowing")}`);
|
|
24555
24638
|
for (const n of v.setupNotes) {
|
|
@@ -24598,6 +24681,17 @@ function renderDesignerBlock(designer2, w) {
|
|
|
24598
24681
|
}
|
|
24599
24682
|
lines.push(chalk12.dim(` ${designerRuleCounts(designer2)}`));
|
|
24600
24683
|
}
|
|
24684
|
+
if (designer2.proposals.length > 0) {
|
|
24685
|
+
lines.push(
|
|
24686
|
+
chalk12.dim(
|
|
24687
|
+
` ${designer2.proposals.length} proposed doc edit${designer2.proposals.length === 1 ? "" : "s"}: iamu design proposals`
|
|
24688
|
+
)
|
|
24689
|
+
);
|
|
24690
|
+
}
|
|
24691
|
+
return lines;
|
|
24692
|
+
}
|
|
24693
|
+
function renderDesignerFindingsBlock(designer2, w) {
|
|
24694
|
+
const lines = [];
|
|
24601
24695
|
for (const g of designerIssuesByScreen(designer2)) {
|
|
24602
24696
|
lines.push(` ${g.screen ?? "Across the run"}`);
|
|
24603
24697
|
for (const i of g.issues) {
|
|
@@ -24616,13 +24710,6 @@ function renderDesignerBlock(designer2, w) {
|
|
|
24616
24710
|
}
|
|
24617
24711
|
}
|
|
24618
24712
|
}
|
|
24619
|
-
if (designer2.proposals.length > 0) {
|
|
24620
|
-
lines.push(
|
|
24621
|
-
chalk12.dim(
|
|
24622
|
-
` ${designer2.proposals.length} proposed doc edit${designer2.proposals.length === 1 ? "" : "s"}: iamu design proposals`
|
|
24623
|
-
)
|
|
24624
|
-
);
|
|
24625
|
-
}
|
|
24626
24713
|
return lines;
|
|
24627
24714
|
}
|
|
24628
24715
|
function renderOverview(v, width = 100) {
|
|
@@ -25777,12 +25864,28 @@ function renderDesigner2(designer2) {
|
|
|
25777
25864
|
const rules = designer2.judged_against === "design-docs" ? `${designer2.rules_broken.length > 0 ? `<div class="card-label designer-gap">Rules broken</div>
|
|
25778
25865
|
<ul class="designer-list">${designer2.rules_broken.map((r) => `<li>${esc(designerBreakLine(r))}</li>`).join("")}</ul>` : ""}
|
|
25779
25866
|
<div class="finding-meta">${esc(designerRuleCounts(designer2))}</div>` : "";
|
|
25867
|
+
const proposals = designer2.proposals.length > 0 ? `<div class="finding-meta designer-gap">${esc(`Proposed doc edits: ${designer2.proposals.map((p) => p.slug).join(", ")}. Decide them with iamu design proposals.`)}</div>` : "";
|
|
25868
|
+
return `
|
|
25869
|
+
<section>
|
|
25870
|
+
<style>${DESIGNER_CSS}
|
|
25871
|
+
</style>
|
|
25872
|
+
<h2>Designer's review</h2>
|
|
25873
|
+
<div class="card">
|
|
25874
|
+
<div class="finding-meta" style="margin-top:0">${esc(designerLabel(designer2))}</div>
|
|
25875
|
+
${designer2.summary ? `<p style="font-size:14px;line-height:1.6;margin-top:0.4rem">${esc(designer2.summary)}</p>` : ""}
|
|
25876
|
+
${rules}
|
|
25877
|
+
${proposals}
|
|
25878
|
+
</div>
|
|
25879
|
+
</section>`;
|
|
25880
|
+
}
|
|
25881
|
+
function renderDesignerFindings2(designer2) {
|
|
25882
|
+
if (designer2.skipped) return "";
|
|
25780
25883
|
const screens = designerIssuesByScreen(designer2).map(
|
|
25781
25884
|
(g) => `
|
|
25782
25885
|
<div class="card-label designer-gap">${esc(g.screen ?? "Across the run")}</div>
|
|
25783
25886
|
${g.issues.map(
|
|
25784
25887
|
(i) => `
|
|
25785
|
-
<div class="designer-item">
|
|
25888
|
+
<div class="finding-item designer-item">
|
|
25786
25889
|
<div class="designer-title">${designerTitle(i)}</div>
|
|
25787
25890
|
<div class="designer-meta">${esc(designerIssueMeta(i))}</div>
|
|
25788
25891
|
<div class="designer-text">${esc([i.what, i.why].filter(Boolean).join(" "))}</div>
|
|
@@ -25790,19 +25893,18 @@ function renderDesigner2(designer2) {
|
|
|
25790
25893
|
</div>`
|
|
25791
25894
|
).join("")}`
|
|
25792
25895
|
).join("");
|
|
25793
|
-
const proposals = designer2.proposals.length > 0 ? `<div class="finding-meta designer-gap">${esc(`Proposed doc edits: ${designer2.proposals.map((p) => p.slug).join(", ")}. Decide them with iamu design proposals.`)}</div>` : "";
|
|
25794
25896
|
return `
|
|
25795
|
-
|
|
25796
|
-
<
|
|
25797
|
-
|
|
25798
|
-
|
|
25799
|
-
|
|
25800
|
-
|
|
25801
|
-
|
|
25802
|
-
|
|
25803
|
-
|
|
25804
|
-
|
|
25805
|
-
|
|
25897
|
+
<div class="finding-meta designer-gap">${esc(designerCount(designer2))}</div>
|
|
25898
|
+
${screens || '<div class="finding-meta">No designer findings beyond the design causes above.</div>'}`;
|
|
25899
|
+
}
|
|
25900
|
+
function renderStepDesignerReading(reading) {
|
|
25901
|
+
if (!reading || !reading.reading && reading.serves.length === 0) return "";
|
|
25902
|
+
return `
|
|
25903
|
+
<div class="designer-item">
|
|
25904
|
+
<div class="designer-meta">Designer reading \xB7 ${esc(reading.key)}</div>
|
|
25905
|
+
${reading.reading ? `<div class="designer-text">${esc(reading.reading)}</div>` : ""}
|
|
25906
|
+
${reading.serves.length > 0 ? `<div class="designer-meta">Serves: ${esc(reading.serves.join("; "))}</div>` : ""}
|
|
25907
|
+
</div>`;
|
|
25806
25908
|
}
|
|
25807
25909
|
function generateHtmlReport(data) {
|
|
25808
25910
|
const {
|
|
@@ -25825,6 +25927,14 @@ function generateHtmlReport(data) {
|
|
|
25825
25927
|
const dateStr = formatDate(completedAt ?? startedAt);
|
|
25826
25928
|
const problems = sortProblems(findings2.filter((f) => f.type !== "helps"));
|
|
25827
25929
|
const causes = designCausesByFinding(data.designer ?? null);
|
|
25930
|
+
const screenReadings = designerScreenReadingsByStep(
|
|
25931
|
+
data.designer ?? null,
|
|
25932
|
+
steps2.map((s) => ({
|
|
25933
|
+
step_number: s.stepNumber,
|
|
25934
|
+
url: s.url,
|
|
25935
|
+
surface: s.screenTitle
|
|
25936
|
+
}))
|
|
25937
|
+
);
|
|
25828
25938
|
const strengths = findings2.filter((f) => f.type === "helps");
|
|
25829
25939
|
const criticalCount = problems.filter(
|
|
25830
25940
|
(f) => f.severity === "critical"
|
|
@@ -26344,7 +26454,9 @@ function generateHtmlReport(data) {
|
|
|
26344
26454
|
</section>
|
|
26345
26455
|
` : ""}
|
|
26346
26456
|
|
|
26347
|
-
${
|
|
26457
|
+
${data.designer ? `${renderDesigner2(data.designer)}
|
|
26458
|
+
|
|
26459
|
+
` : ""}${verification ? renderSinceTable(verification) : ""}
|
|
26348
26460
|
|
|
26349
26461
|
<!-- Findings, worst first -->
|
|
26350
26462
|
${problems.length > 0 ? `
|
|
@@ -26364,13 +26476,16 @@ function generateHtmlReport(data) {
|
|
|
26364
26476
|
${f.recommendation ? `<div class="finding-meta finding-rec"><strong>Recommendation:</strong> ${inlineMarkdown(f.recommendation)}</div>` : ""}${renderDesignCauses2(causes.get(f.id) ?? [])}
|
|
26365
26477
|
</div>
|
|
26366
26478
|
`
|
|
26367
|
-
).join("")}
|
|
26479
|
+
).join("")}${data.designer ? renderDesignerFindings2(data.designer) : ""}
|
|
26480
|
+
</section>
|
|
26481
|
+
` : data.designer && !data.designer.skipped ? `
|
|
26482
|
+
<section>
|
|
26483
|
+
<h2>Findings (0)</h2>${renderDesignerFindings2(data.designer)}
|
|
26368
26484
|
</section>
|
|
26369
26485
|
` : ""}
|
|
26370
26486
|
|
|
26371
26487
|
${[
|
|
26372
26488
|
data.analysis ? renderDirections2(data.analysis) : "",
|
|
26373
|
-
data.designer ? renderDesigner2(data.designer) : "",
|
|
26374
26489
|
data.analysis ? renderNotes2(data.analysis) : ""
|
|
26375
26490
|
].filter(Boolean).map((section) => `${section}
|
|
26376
26491
|
|
|
@@ -26426,7 +26541,7 @@ function generateHtmlReport(data) {
|
|
|
26426
26541
|
${s.personaReasoning ? `
|
|
26427
26542
|
<div class="step-thought">
|
|
26428
26543
|
<div class="prose-quote">${esc(s.personaReasoning)}</div>
|
|
26429
|
-
</div>` : ""}
|
|
26544
|
+
</div>` : ""}${renderStepDesignerReading(screenReadings.get(s.stepNumber))}
|
|
26430
26545
|
${renderStepScreenshotHtml(s)}
|
|
26431
26546
|
</div>
|
|
26432
26547
|
</div>
|
|
@@ -26648,6 +26763,7 @@ __export(report_exports, {
|
|
|
26648
26763
|
report: () => report
|
|
26649
26764
|
});
|
|
26650
26765
|
import path48 from "path";
|
|
26766
|
+
import { pathToFileURL } from "url";
|
|
26651
26767
|
import chalk15 from "chalk";
|
|
26652
26768
|
async function report(scenario, opts) {
|
|
26653
26769
|
const ctx = await resolveProject(opts);
|
|
@@ -26749,7 +26865,7 @@ async function htmlReport(projectRoot, target, opts) {
|
|
|
26749
26865
|
console.log();
|
|
26750
26866
|
if (opts.open) {
|
|
26751
26867
|
const { openBrowser: openBrowser2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
26752
|
-
openBrowser2(
|
|
26868
|
+
openBrowser2(pathToFileURL(filePath).href);
|
|
26753
26869
|
}
|
|
26754
26870
|
}
|
|
26755
26871
|
async function jsonRows(projectRoot, runs) {
|
|
@@ -26805,6 +26921,7 @@ __export(open_exports, {
|
|
|
26805
26921
|
});
|
|
26806
26922
|
import { existsSync as existsSync12, promises as fs54 } from "fs";
|
|
26807
26923
|
import path49 from "path";
|
|
26924
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
26808
26925
|
import { isCancel as isCancel3, select as select3 } from "@clack/prompts";
|
|
26809
26926
|
import chalk16 from "chalk";
|
|
26810
26927
|
async function openDashboard(runId, opts) {
|
|
@@ -26932,7 +27049,7 @@ async function openLocalReport(projectRoot, runId, opts) {
|
|
|
26932
27049
|
console.log(` ${symbols.success} Opening local walkthrough report:`);
|
|
26933
27050
|
console.log(` ${chalk16.cyan(filePath)}`);
|
|
26934
27051
|
console.log();
|
|
26935
|
-
openBrowser(
|
|
27052
|
+
openBrowser(pathToFileURL2(filePath).href);
|
|
26936
27053
|
}
|
|
26937
27054
|
var init_open = __esm({
|
|
26938
27055
|
"src/commands/open.ts"() {
|
|
@@ -29473,7 +29590,7 @@ __export(init_exports, {
|
|
|
29473
29590
|
init: () => init,
|
|
29474
29591
|
setupCommand: () => setupCommand
|
|
29475
29592
|
});
|
|
29476
|
-
import { existsSync as existsSync13, readFileSync as readFileSync6 } from "fs";
|
|
29593
|
+
import { existsSync as existsSync13, readFileSync as readFileSync6, readdirSync as readdirSync3 } from "fs";
|
|
29477
29594
|
import { mkdir as mkdir4, readdir, writeFile as writeFile3 } from "fs/promises";
|
|
29478
29595
|
import { basename as basename2, join as join6 } from "path";
|
|
29479
29596
|
import { isCancel as isCancel4, multiselect, select as select4 } from "@clack/prompts";
|
|
@@ -29495,13 +29612,9 @@ function setupCommand(projectRoot, hosts) {
|
|
|
29495
29612
|
if (hosts.every((h) => h.value === "claude")) return "/iamu:init";
|
|
29496
29613
|
return "/iamu-setup (Claude Code: /iamu:init)";
|
|
29497
29614
|
}
|
|
29498
|
-
function
|
|
29499
|
-
if (process.env.PORT) {
|
|
29500
|
-
const p = Number.parseInt(process.env.PORT, 10);
|
|
29501
|
-
if (!Number.isNaN(p) && p > 0 && p <= 65535) return p;
|
|
29502
|
-
}
|
|
29615
|
+
function detectDirPort(dir) {
|
|
29503
29616
|
for (const envFile of [".env.local", ".env.development", ".env"]) {
|
|
29504
|
-
const fullPath = join6(
|
|
29617
|
+
const fullPath = join6(dir, envFile);
|
|
29505
29618
|
if (existsSync13(fullPath)) {
|
|
29506
29619
|
try {
|
|
29507
29620
|
const content = readFileSync6(fullPath, "utf-8");
|
|
@@ -29527,7 +29640,29 @@ function detectTargetPort(projectRoot) {
|
|
|
29527
29640
|
}
|
|
29528
29641
|
}
|
|
29529
29642
|
}
|
|
29530
|
-
const
|
|
29643
|
+
for (const f of [
|
|
29644
|
+
"vite.config.ts",
|
|
29645
|
+
"vite.config.js",
|
|
29646
|
+
"vite.config.mjs",
|
|
29647
|
+
"vite.config.cjs",
|
|
29648
|
+
"astro.config.mjs",
|
|
29649
|
+
"astro.config.ts",
|
|
29650
|
+
"astro.config.js"
|
|
29651
|
+
]) {
|
|
29652
|
+
const cfgPath = join6(dir, f);
|
|
29653
|
+
if (existsSync13(cfgPath)) {
|
|
29654
|
+
try {
|
|
29655
|
+
const raw = readFileSync6(cfgPath, "utf-8");
|
|
29656
|
+
const portMatch = raw.match(/\bport\s*:\s*(\d{2,5})\b/);
|
|
29657
|
+
if (portMatch) {
|
|
29658
|
+
const p = Number.parseInt(portMatch[1], 10);
|
|
29659
|
+
if (!Number.isNaN(p) && p > 0 && p <= 65535) return p;
|
|
29660
|
+
}
|
|
29661
|
+
} catch {
|
|
29662
|
+
}
|
|
29663
|
+
}
|
|
29664
|
+
}
|
|
29665
|
+
const pkgPath = join6(dir, "package.json");
|
|
29531
29666
|
if (existsSync13(pkgPath)) {
|
|
29532
29667
|
try {
|
|
29533
29668
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
@@ -29546,11 +29681,12 @@ function detectTargetPort(projectRoot) {
|
|
|
29546
29681
|
};
|
|
29547
29682
|
if ("astro" in allDeps) return 4321;
|
|
29548
29683
|
if ("vite" in allDeps) return 5173;
|
|
29684
|
+
if ("next" in allDeps) return 3e3;
|
|
29549
29685
|
} catch {
|
|
29550
29686
|
}
|
|
29551
29687
|
}
|
|
29552
29688
|
for (const f of ["astro.config.mjs", "astro.config.ts", "astro.config.js"]) {
|
|
29553
|
-
if (existsSync13(join6(
|
|
29689
|
+
if (existsSync13(join6(dir, f))) return 4321;
|
|
29554
29690
|
}
|
|
29555
29691
|
for (const f of [
|
|
29556
29692
|
"vite.config.ts",
|
|
@@ -29558,7 +29694,47 @@ function detectTargetPort(projectRoot) {
|
|
|
29558
29694
|
"vite.config.mjs",
|
|
29559
29695
|
"vite.config.cjs"
|
|
29560
29696
|
]) {
|
|
29561
|
-
if (existsSync13(join6(
|
|
29697
|
+
if (existsSync13(join6(dir, f))) return 5173;
|
|
29698
|
+
}
|
|
29699
|
+
return void 0;
|
|
29700
|
+
}
|
|
29701
|
+
function detectTargetPort(projectRoot) {
|
|
29702
|
+
if (process.env.PORT) {
|
|
29703
|
+
const p = Number.parseInt(process.env.PORT, 10);
|
|
29704
|
+
if (!Number.isNaN(p) && p > 0 && p <= 65535) return p;
|
|
29705
|
+
}
|
|
29706
|
+
const rootPort = detectDirPort(projectRoot);
|
|
29707
|
+
if (rootPort !== void 0) return rootPort;
|
|
29708
|
+
const preferredSubdirs = [
|
|
29709
|
+
"apps/web",
|
|
29710
|
+
"apps/app",
|
|
29711
|
+
"apps/frontend",
|
|
29712
|
+
"apps/client",
|
|
29713
|
+
"packages/web",
|
|
29714
|
+
"packages/app",
|
|
29715
|
+
"packages/frontend",
|
|
29716
|
+
"web",
|
|
29717
|
+
"frontend",
|
|
29718
|
+
"client"
|
|
29719
|
+
];
|
|
29720
|
+
for (const rel of preferredSubdirs) {
|
|
29721
|
+
const full = join6(projectRoot, rel);
|
|
29722
|
+
if (existsSync13(full)) {
|
|
29723
|
+
const p = detectDirPort(full);
|
|
29724
|
+
if (p !== void 0) return p;
|
|
29725
|
+
}
|
|
29726
|
+
}
|
|
29727
|
+
for (const parent of ["apps", "packages"]) {
|
|
29728
|
+
const parentDir = join6(projectRoot, parent);
|
|
29729
|
+
if (!existsSync13(parentDir)) continue;
|
|
29730
|
+
try {
|
|
29731
|
+
for (const entry of readdirSync3(parentDir, { withFileTypes: true })) {
|
|
29732
|
+
if (!entry.isDirectory()) continue;
|
|
29733
|
+
const p = detectDirPort(join6(parentDir, entry.name));
|
|
29734
|
+
if (p !== void 0) return p;
|
|
29735
|
+
}
|
|
29736
|
+
} catch {
|
|
29737
|
+
}
|
|
29562
29738
|
}
|
|
29563
29739
|
return void 0;
|
|
29564
29740
|
}
|
|
@@ -29566,8 +29742,9 @@ function detectTargetUrl(projectRoot) {
|
|
|
29566
29742
|
const port = detectTargetPort(projectRoot);
|
|
29567
29743
|
return port ? `http://localhost:${port}` : "http://localhost:3000";
|
|
29568
29744
|
}
|
|
29569
|
-
function createDraftTestabilityContent(targetUrl = "http://localhost:3000") {
|
|
29745
|
+
function createDraftTestabilityContent(targetUrl = "http://localhost:3000", status2 = "draft") {
|
|
29570
29746
|
const doc = emptyTestability(targetUrl);
|
|
29747
|
+
doc.frontmatter.status = status2;
|
|
29571
29748
|
return serializeFrontmatter(doc.frontmatter, doc.body);
|
|
29572
29749
|
}
|
|
29573
29750
|
async function init(opts) {
|
|
@@ -29591,12 +29768,28 @@ async function init(opts) {
|
|
|
29591
29768
|
if (existsSync13(join6(iamuDir2, "config.json"))) {
|
|
29592
29769
|
const testabilityPath2 = join6(iamuDir2, "testability.md");
|
|
29593
29770
|
if (!existsSync13(testabilityPath2)) {
|
|
29594
|
-
const targetUrl = detectTargetUrl(projectRoot);
|
|
29771
|
+
const targetUrl = options.target || detectTargetUrl(projectRoot);
|
|
29595
29772
|
await writeFile3(
|
|
29596
29773
|
testabilityPath2,
|
|
29597
|
-
createDraftTestabilityContent(
|
|
29774
|
+
createDraftTestabilityContent(
|
|
29775
|
+
targetUrl,
|
|
29776
|
+
options.target ? "verified" : "draft"
|
|
29777
|
+
)
|
|
29598
29778
|
);
|
|
29599
29779
|
backfilled.push("testability.md");
|
|
29780
|
+
} else if (options.target) {
|
|
29781
|
+
const existing = await readTestability(projectRoot);
|
|
29782
|
+
const fm = existing?.frontmatter ?? {
|
|
29783
|
+
schema_version: 1,
|
|
29784
|
+
status: "verified",
|
|
29785
|
+
target_url: options.target
|
|
29786
|
+
};
|
|
29787
|
+
fm.target_url = options.target;
|
|
29788
|
+
fm.status = "verified";
|
|
29789
|
+
await writeFile3(
|
|
29790
|
+
testabilityPath2,
|
|
29791
|
+
serializeFrontmatter(fm, existing?.body ?? "")
|
|
29792
|
+
);
|
|
29600
29793
|
}
|
|
29601
29794
|
const contextPath2 = join6(iamuDir2, "context.yml");
|
|
29602
29795
|
if (!existsSync13(contextPath2)) {
|
|
@@ -29684,10 +29877,13 @@ async function init(opts) {
|
|
|
29684
29877
|
);
|
|
29685
29878
|
const testabilityPath2 = join6(iamuDir2, "testability.md");
|
|
29686
29879
|
if (!existsSync13(testabilityPath2)) {
|
|
29687
|
-
const targetUrl = detectTargetUrl(projectRoot);
|
|
29880
|
+
const targetUrl = options.target || detectTargetUrl(projectRoot);
|
|
29688
29881
|
await writeFile3(
|
|
29689
29882
|
testabilityPath2,
|
|
29690
|
-
createDraftTestabilityContent(
|
|
29883
|
+
createDraftTestabilityContent(
|
|
29884
|
+
targetUrl,
|
|
29885
|
+
options.target ? "verified" : "draft"
|
|
29886
|
+
)
|
|
29691
29887
|
);
|
|
29692
29888
|
}
|
|
29693
29889
|
}
|
|
@@ -29804,6 +30000,9 @@ async function init(opts) {
|
|
|
29804
30000
|
}
|
|
29805
30001
|
}
|
|
29806
30002
|
}
|
|
30003
|
+
const testabilityDoc = await readTestability(projectRoot).catch(() => null);
|
|
30004
|
+
const configuredTarget = testabilityDoc?.frontmatter.target_url ?? detectTargetUrl(projectRoot);
|
|
30005
|
+
const targetStatus = testabilityDoc?.frontmatter.status ?? "draft";
|
|
29807
30006
|
if (options.json) {
|
|
29808
30007
|
console.log(
|
|
29809
30008
|
JSON.stringify(
|
|
@@ -29813,6 +30012,8 @@ async function init(opts) {
|
|
|
29813
30012
|
projectRoot,
|
|
29814
30013
|
fresh: freshInit,
|
|
29815
30014
|
backfilled,
|
|
30015
|
+
targetUrl: configuredTarget,
|
|
30016
|
+
targetStatus,
|
|
29816
30017
|
tools: selectedHosts.map((h) => h.name),
|
|
29817
30018
|
skillsCount: totalSkills,
|
|
29818
30019
|
commandsCount: totalCommands,
|
|
@@ -29834,6 +30035,9 @@ async function init(opts) {
|
|
|
29834
30035
|
)
|
|
29835
30036
|
);
|
|
29836
30037
|
console.log();
|
|
30038
|
+
console.log(
|
|
30039
|
+
` ${chalk27.dim("Target:")} ${chalk27.cyan(configuredTarget)} ${chalk27.dim(targetStatus === "verified" ? "(verified)" : "(detected \u2014 change anytime with `iamu set testability --target <url>`)")}`
|
|
30040
|
+
);
|
|
29837
30041
|
if (selectedHosts.length > 0) {
|
|
29838
30042
|
const toolNames = selectedHosts.map((h) => h.name).join(", ");
|
|
29839
30043
|
console.log(
|
|
@@ -29982,6 +30186,10 @@ async function status(opts) {
|
|
|
29982
30186
|
const incompleteRuns = await listIncompleteRuns(projectRoot);
|
|
29983
30187
|
const { rerun } = await computeFixStates(projectRoot);
|
|
29984
30188
|
const staleSkills = await findStaleSkills(projectRoot);
|
|
30189
|
+
const testability = await readTestability(projectRoot).catch(() => null);
|
|
30190
|
+
const designState = await readDesignSources(projectRoot).catch(() => null);
|
|
30191
|
+
const designDocs = designState?.file.files.filter((f) => f.kind === "design") ?? [];
|
|
30192
|
+
const offerDesignTemplate = designDocs.length === 0 ? await shouldOfferTemplate(projectRoot).catch(() => false) : false;
|
|
29985
30193
|
let exploreSummary = null;
|
|
29986
30194
|
try {
|
|
29987
30195
|
const maps = await listExploreMaps(projectRoot);
|
|
@@ -30007,6 +30215,21 @@ async function status(opts) {
|
|
|
30007
30215
|
{
|
|
30008
30216
|
project: projectStatus.project.name,
|
|
30009
30217
|
mode: projectStatus.cloud.linked ? "cloud-linked" : "local-only",
|
|
30218
|
+
target: testability ? {
|
|
30219
|
+
url: testability.frontmatter.target_url,
|
|
30220
|
+
status: testability.frontmatter.status ?? "draft",
|
|
30221
|
+
dev_command: testability.frontmatter.dev_command ?? null,
|
|
30222
|
+
auth_strategy: testability.frontmatter.auth_strategy ?? null,
|
|
30223
|
+
access: testability.frontmatter.access ?? null
|
|
30224
|
+
} : null,
|
|
30225
|
+
design: {
|
|
30226
|
+
recorded: designDocs.map((f) => f.path),
|
|
30227
|
+
sources: designState?.file.files.map((f) => ({
|
|
30228
|
+
path: f.path,
|
|
30229
|
+
kind: f.kind
|
|
30230
|
+
})) ?? [],
|
|
30231
|
+
offer_template: offerDesignTemplate
|
|
30232
|
+
},
|
|
30010
30233
|
entities: Object.fromEntries(
|
|
30011
30234
|
Object.entries(projectStatus.entities).map(([k, v]) => [
|
|
30012
30235
|
k,
|
|
@@ -30092,6 +30315,18 @@ async function status(opts) {
|
|
|
30092
30315
|
console.log(` ${padRight(label, 14)} ${num2}${inv}`);
|
|
30093
30316
|
}
|
|
30094
30317
|
if (!quiet) console.log();
|
|
30318
|
+
if (testability?.frontmatter.target_url) {
|
|
30319
|
+
const tfm = testability.frontmatter;
|
|
30320
|
+
const isVerified = tfm.status === "verified";
|
|
30321
|
+
const meta = [
|
|
30322
|
+
isVerified ? "verified" : "unverified",
|
|
30323
|
+
tfm.auth_strategy ? `auth: ${tfm.auth_strategy}` : null,
|
|
30324
|
+
tfm.dev_command ? `dev: ${tfm.dev_command}` : null
|
|
30325
|
+
].filter(Boolean).join(" \xB7 ");
|
|
30326
|
+
console.log(
|
|
30327
|
+
` ${padRight("target", 14)} ${chalk28.cyan(tfm.target_url)}${chalk28.dim(` (${meta})`)}${!isVerified ? chalk28.dim(" \u2014 `iamu set testability --target <url>`") : ""}`
|
|
30328
|
+
);
|
|
30329
|
+
}
|
|
30095
30330
|
let ctxLabel = chalk28.dim("none");
|
|
30096
30331
|
if (projectStatus.context.present) {
|
|
30097
30332
|
if (projectStatus.context.richness === "thin") {
|
|
@@ -30102,6 +30337,19 @@ async function status(opts) {
|
|
|
30102
30337
|
}
|
|
30103
30338
|
}
|
|
30104
30339
|
console.log(` ${padRight("context", 14)} ${ctxLabel}`);
|
|
30340
|
+
if (designDocs.length > 0) {
|
|
30341
|
+
console.log(
|
|
30342
|
+
` ${padRight("design", 14)} ${chalk28.green(designDocs.map((f) => f.path).join(", "))}`
|
|
30343
|
+
);
|
|
30344
|
+
} else if (offerDesignTemplate) {
|
|
30345
|
+
console.log(
|
|
30346
|
+
` ${padRight("design", 14)} ${chalk28.yellow("none")}${chalk28.dim(" (general craft only \u2014 run `iamu-design` so runs check your own design rules)")}`
|
|
30347
|
+
);
|
|
30348
|
+
} else {
|
|
30349
|
+
console.log(
|
|
30350
|
+
` ${padRight("design", 14)} ${chalk28.dim("none (general craft)")}`
|
|
30351
|
+
);
|
|
30352
|
+
}
|
|
30105
30353
|
console.log(
|
|
30106
30354
|
` ${padRight("runs", 14)} ${runs.length === 0 ? chalk28.dim("0") : chalk28.cyan(String(runs.length))}${runs.length > 0 ? chalk28.dim(" last: " + runs.at(-1)) : ""}`
|
|
30107
30355
|
);
|
|
@@ -30441,7 +30689,8 @@ async function fillOrSelect(el, text, submit) {
|
|
|
30441
30689
|
if (submit) await el.press("Enter");
|
|
30442
30690
|
}
|
|
30443
30691
|
async function markAndQueryIndexedElements(page) {
|
|
30444
|
-
|
|
30692
|
+
if (typeof page.evaluate !== "function") return [];
|
|
30693
|
+
const evaluated = await page.evaluate((selector) => {
|
|
30445
30694
|
const g = globalThis;
|
|
30446
30695
|
function collectElements(root) {
|
|
30447
30696
|
const result = [];
|
|
@@ -30466,6 +30715,41 @@ async function markAndQueryIndexedElements(page) {
|
|
|
30466
30715
|
}
|
|
30467
30716
|
const elements = collectElements(g.document);
|
|
30468
30717
|
const results = [];
|
|
30718
|
+
function normalizeText(raw) {
|
|
30719
|
+
return (raw || "").trim().replace(/\s+/g, " ").slice(0, 80);
|
|
30720
|
+
}
|
|
30721
|
+
function getAssociatedLabel(el) {
|
|
30722
|
+
const labelledBy = el.getAttribute("aria-labelledby");
|
|
30723
|
+
if (labelledBy && typeof g.document.getElementById === "function") {
|
|
30724
|
+
const parts = [];
|
|
30725
|
+
for (const id of labelledBy.split(/\s+/)) {
|
|
30726
|
+
if (!id) continue;
|
|
30727
|
+
const node = g.document.getElementById(id);
|
|
30728
|
+
const txt = normalizeText(node?.textContent);
|
|
30729
|
+
if (txt) parts.push(txt);
|
|
30730
|
+
}
|
|
30731
|
+
if (parts.length > 0) return normalizeText(parts.join(" "));
|
|
30732
|
+
}
|
|
30733
|
+
if (el.labels && el.labels.length > 0) {
|
|
30734
|
+
const parts = [];
|
|
30735
|
+
for (let i = 0; i < el.labels.length; i++) {
|
|
30736
|
+
const lbl = el.labels[i];
|
|
30737
|
+
const txt = normalizeText(lbl?.innerText ?? lbl?.textContent);
|
|
30738
|
+
if (txt) parts.push(txt);
|
|
30739
|
+
}
|
|
30740
|
+
if (parts.length > 0) return normalizeText(parts.join(" "));
|
|
30741
|
+
}
|
|
30742
|
+
if (typeof el.closest === "function") {
|
|
30743
|
+
const parentLabel = el.closest("label");
|
|
30744
|
+
if (parentLabel) {
|
|
30745
|
+
const txt = normalizeText(
|
|
30746
|
+
parentLabel.innerText ?? parentLabel.textContent
|
|
30747
|
+
);
|
|
30748
|
+
if (txt) return txt;
|
|
30749
|
+
}
|
|
30750
|
+
}
|
|
30751
|
+
return "";
|
|
30752
|
+
}
|
|
30469
30753
|
let count = 0;
|
|
30470
30754
|
for (const el of elements) {
|
|
30471
30755
|
const style = g.getComputedStyle(el);
|
|
@@ -30480,7 +30764,10 @@ async function markAndQueryIndexedElements(page) {
|
|
|
30480
30764
|
const roleAttr = el.getAttribute("role");
|
|
30481
30765
|
const type2 = tag === "input" ? el.type?.toLowerCase() || "text" : void 0;
|
|
30482
30766
|
const disabled = Boolean(el.disabled) || el.getAttribute("aria-disabled") === "true";
|
|
30483
|
-
const
|
|
30767
|
+
const isCheckable = type2 === "radio" || type2 === "checkbox" || roleAttr === "radio" || roleAttr === "checkbox" || roleAttr === "switch";
|
|
30768
|
+
const checked = isCheckable ? Boolean(el.checked) || el.getAttribute("aria-checked") === "true" : void 0;
|
|
30769
|
+
const labelText = tag === "input" || tag === "select" || tag === "textarea" ? getAssociatedLabel(el) : "";
|
|
30770
|
+
const name = normalizeText(el.getAttribute("aria-label")) || labelText || normalizeText(el.getAttribute("title")) || normalizeText(el.placeholder) || (tag === "input" || tag === "textarea" ? normalizeText(el.value) : "") || normalizeText(el.textContent) || "";
|
|
30484
30771
|
const role = roleAttr || (tag === "button" ? "button" : tag === "a" ? "link" : tag === "input" ? type2 || "textbox" : tag === "select" ? "combobox" : tag);
|
|
30485
30772
|
results.push({
|
|
30486
30773
|
index: count,
|
|
@@ -30488,11 +30775,38 @@ async function markAndQueryIndexedElements(page) {
|
|
|
30488
30775
|
role,
|
|
30489
30776
|
name,
|
|
30490
30777
|
...type2 ? { type: type2 } : {},
|
|
30491
|
-
...disabled ? { disabled: true } : {}
|
|
30778
|
+
...disabled ? { disabled: true } : {},
|
|
30779
|
+
...checked ? { checked: true } : {}
|
|
30492
30780
|
});
|
|
30493
30781
|
}
|
|
30494
30782
|
return results;
|
|
30495
30783
|
}, INDEXED_SELECTOR);
|
|
30784
|
+
return Array.isArray(evaluated) ? evaluated : [];
|
|
30785
|
+
}
|
|
30786
|
+
async function clickLocatorWithFallback(loc, timeoutMs = ACTION_TIMEOUT_MS) {
|
|
30787
|
+
if (typeof loc.count !== "function") {
|
|
30788
|
+
await loc.click({ timeout: timeoutMs });
|
|
30789
|
+
return;
|
|
30790
|
+
}
|
|
30791
|
+
const presentCount = await loc.count().catch(() => 0);
|
|
30792
|
+
if (presentCount > 0) {
|
|
30793
|
+
try {
|
|
30794
|
+
await loc.click({ timeout: 250 });
|
|
30795
|
+
return;
|
|
30796
|
+
} catch (err) {
|
|
30797
|
+
if (typeof loc.evaluate === "function") {
|
|
30798
|
+
await loc.evaluate((node) => {
|
|
30799
|
+
const el = node;
|
|
30800
|
+
if (typeof el.click === "function") {
|
|
30801
|
+
el.click();
|
|
30802
|
+
}
|
|
30803
|
+
});
|
|
30804
|
+
return;
|
|
30805
|
+
}
|
|
30806
|
+
throw err;
|
|
30807
|
+
}
|
|
30808
|
+
}
|
|
30809
|
+
await loc.click({ timeout: Math.min(timeoutMs, 1500) });
|
|
30496
30810
|
}
|
|
30497
30811
|
async function clickIndexedElement(page, index) {
|
|
30498
30812
|
let loc = page.locator(`[data-iamu-index="${index}"]`);
|
|
@@ -30505,7 +30819,19 @@ async function clickIndexedElement(page, index) {
|
|
|
30505
30819
|
`Nothing on the page matches interactive element [#${index}]. Run \`iamu browse snapshot --indexed\` to view available elements.`
|
|
30506
30820
|
);
|
|
30507
30821
|
}
|
|
30508
|
-
|
|
30822
|
+
const first = loc.first();
|
|
30823
|
+
try {
|
|
30824
|
+
await first.click({ timeout: 250 });
|
|
30825
|
+
} catch (err) {
|
|
30826
|
+
if (typeof first.evaluate === "function") {
|
|
30827
|
+
await first.evaluate((node) => {
|
|
30828
|
+
const el = node;
|
|
30829
|
+
if (typeof el.click === "function") el.click();
|
|
30830
|
+
});
|
|
30831
|
+
return;
|
|
30832
|
+
}
|
|
30833
|
+
throw err;
|
|
30834
|
+
}
|
|
30509
30835
|
}
|
|
30510
30836
|
async function fillIndexedElement(page, index, text, submit) {
|
|
30511
30837
|
let loc = page.locator(`[data-iamu-index="${index}"]`);
|
|
@@ -30639,13 +30965,24 @@ var init_browse_actions = __esm({
|
|
|
30639
30965
|
usage: 'click <role> "<name>" [--nth N]',
|
|
30640
30966
|
minArgs: 2,
|
|
30641
30967
|
target: roleTarget,
|
|
30642
|
-
run: (p, args2, opts) =>
|
|
30968
|
+
run: async (p, args2, opts) => {
|
|
30969
|
+
const [role, name] = args2;
|
|
30970
|
+
const loc = byRole(p, role, name, opts);
|
|
30971
|
+
if ((role === "radio" || role === "checkbox") && typeof loc.count === "function" && await loc.count().catch(() => 1) === 0 && typeof p.locator === "function") {
|
|
30972
|
+
const byValue = p.locator(`input[type="${role}"][value=${JSON.stringify(name)}]`).first();
|
|
30973
|
+
if (await byValue.count().catch(() => 0) > 0) {
|
|
30974
|
+
await clickLocatorWithFallback(byValue, 1500);
|
|
30975
|
+
return;
|
|
30976
|
+
}
|
|
30977
|
+
}
|
|
30978
|
+
await clickLocatorWithFallback(loc, ACTION_TIMEOUT_MS);
|
|
30979
|
+
}
|
|
30643
30980
|
},
|
|
30644
30981
|
"click-text": {
|
|
30645
30982
|
usage: 'click-text "<visible text>"',
|
|
30646
30983
|
minArgs: 1,
|
|
30647
30984
|
target: (args2) => `the text \u201C${args2[0]}\u201D`,
|
|
30648
|
-
run: (p, args2) => p.getByText(args2[0]).first()
|
|
30985
|
+
run: (p, args2) => clickLocatorWithFallback(p.getByText(args2[0]).first(), ACTION_TIMEOUT_MS)
|
|
30649
30986
|
},
|
|
30650
30987
|
fill: fillAction("fill"),
|
|
30651
30988
|
select: fillAction("select"),
|
|
@@ -30740,7 +31077,7 @@ var init_browse_actions = __esm({
|
|
|
30740
31077
|
});
|
|
30741
31078
|
|
|
30742
31079
|
// src/commands/browse-driver.ts
|
|
30743
|
-
import { execFile as execFile3, spawn } from "child_process";
|
|
31080
|
+
import { execFile as execFile3, execFileSync, spawn } from "child_process";
|
|
30744
31081
|
import { readFile as readFile5 } from "fs/promises";
|
|
30745
31082
|
async function probeCdp(port, timeoutMs = CDP_PROBE_TIMEOUT_MS) {
|
|
30746
31083
|
try {
|
|
@@ -30755,7 +31092,29 @@ async function probeCdp(port, timeoutMs = CDP_PROBE_TIMEOUT_MS) {
|
|
|
30755
31092
|
}
|
|
30756
31093
|
}
|
|
30757
31094
|
async function readProcessCommandLine(pid, platform2 = process.platform) {
|
|
30758
|
-
if (!Number.isInteger(pid) || pid <= 0
|
|
31095
|
+
if (!Number.isInteger(pid) || pid <= 0) return null;
|
|
31096
|
+
if (platform2 === "win32") {
|
|
31097
|
+
try {
|
|
31098
|
+
const out2 = execFileSync(
|
|
31099
|
+
"powershell.exe",
|
|
31100
|
+
[
|
|
31101
|
+
"-NoProfile",
|
|
31102
|
+
"-NonInteractive",
|
|
31103
|
+
"-Command",
|
|
31104
|
+
`(Get-CimInstance Win32_Process -Filter 'ProcessId = ${pid}').CommandLine`
|
|
31105
|
+
],
|
|
31106
|
+
{
|
|
31107
|
+
encoding: "utf-8",
|
|
31108
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
31109
|
+
timeout: 3e3,
|
|
31110
|
+
windowsHide: true
|
|
31111
|
+
}
|
|
31112
|
+
);
|
|
31113
|
+
return out2.trim() || null;
|
|
31114
|
+
} catch {
|
|
31115
|
+
return null;
|
|
31116
|
+
}
|
|
31117
|
+
}
|
|
30759
31118
|
if (platform2 === "linux") {
|
|
30760
31119
|
const raw = await readFile5(`/proc/${pid}/cmdline`, "utf8").catch(() => "");
|
|
30761
31120
|
if (raw) return raw.split("\0").join(" ").trim();
|
|
@@ -30770,13 +31129,32 @@ async function readProcessCommandLine(pid, platform2 = process.platform) {
|
|
|
30770
31129
|
});
|
|
30771
31130
|
}
|
|
30772
31131
|
function isOurBrowser(commandLine, profileDir2) {
|
|
30773
|
-
if (!commandLine) return false;
|
|
30774
|
-
|
|
30775
|
-
|
|
30776
|
-
const
|
|
30777
|
-
|
|
30778
|
-
|
|
30779
|
-
|
|
31132
|
+
if (!commandLine || !BROWSER_NAME_RE.test(commandLine)) return false;
|
|
31133
|
+
if (!profileDir2) return false;
|
|
31134
|
+
const matches = (cmd, dir) => {
|
|
31135
|
+
const flags = [
|
|
31136
|
+
`--user-data-dir=${dir}`,
|
|
31137
|
+
`--user-data-dir="${dir}"`,
|
|
31138
|
+
`--user-data-dir='${dir}'`,
|
|
31139
|
+
`--user-data-dir="${dir}`,
|
|
31140
|
+
`--user-data-dir='${dir}`
|
|
31141
|
+
];
|
|
31142
|
+
for (const flag of flags) {
|
|
31143
|
+
for (let i = cmd.indexOf(flag); i !== -1; ) {
|
|
31144
|
+
const next = cmd[i + flag.length];
|
|
31145
|
+
if (next === void 0 || next === " " || next === " " || next === "\r" || next === "\n" || next === `"` || next === `'`) {
|
|
31146
|
+
return true;
|
|
31147
|
+
}
|
|
31148
|
+
i = cmd.indexOf(flag, i + 1);
|
|
31149
|
+
}
|
|
31150
|
+
}
|
|
31151
|
+
return false;
|
|
31152
|
+
};
|
|
31153
|
+
if (matches(commandLine, profileDir2)) return true;
|
|
31154
|
+
const normCmd = commandLine.replace(/\\/g, "/");
|
|
31155
|
+
const normDir = profileDir2.replace(/\\/g, "/");
|
|
31156
|
+
if (matches(normCmd, normDir)) return true;
|
|
31157
|
+
if (matches(normCmd.toLowerCase(), normDir.toLowerCase())) return true;
|
|
30780
31158
|
return false;
|
|
30781
31159
|
}
|
|
30782
31160
|
async function reapBrowser(pid, profileDir2, driver) {
|
|
@@ -30785,15 +31163,28 @@ async function reapBrowser(pid, profileDir2, driver) {
|
|
|
30785
31163
|
driver.kill(pid);
|
|
30786
31164
|
return true;
|
|
30787
31165
|
}
|
|
31166
|
+
function selectTargetPage(rawPages, pageIndex) {
|
|
31167
|
+
const nonDevtools = rawPages.filter((p) => !p.url().startsWith("devtools:"));
|
|
31168
|
+
const userPages = nonDevtools.filter(
|
|
31169
|
+
(p) => !/^(?:chrome-extension|chrome|edge):/i.test(p.url())
|
|
31170
|
+
);
|
|
31171
|
+
const pages = userPages.length > 0 ? userPages : nonDevtools;
|
|
31172
|
+
if (pageIndex !== void 0 && pages[pageIndex]) {
|
|
31173
|
+
return { page: pages[pageIndex], pages };
|
|
31174
|
+
}
|
|
31175
|
+
const preferred = pages.filter((p) => p.url() && p.url() !== "about:blank").at(-1);
|
|
31176
|
+
return { page: preferred ?? pages.at(-1), pages };
|
|
31177
|
+
}
|
|
30788
31178
|
async function connect(port) {
|
|
30789
31179
|
const { chromium } = await import("playwright-core");
|
|
30790
31180
|
return chromium.connectOverCDP(`http://127.0.0.1:${port}`);
|
|
30791
31181
|
}
|
|
30792
|
-
var CDP_PROBE_TIMEOUT_MS, cdpDriver;
|
|
31182
|
+
var CDP_PROBE_TIMEOUT_MS, BROWSER_NAME_RE, cdpDriver;
|
|
30793
31183
|
var init_browse_driver = __esm({
|
|
30794
31184
|
"src/commands/browse-driver.ts"() {
|
|
30795
31185
|
"use strict";
|
|
30796
31186
|
CDP_PROBE_TIMEOUT_MS = 1e3;
|
|
31187
|
+
BROWSER_NAME_RE = /(?:chrome|chromium|brave|msedge)/i;
|
|
30797
31188
|
cdpDriver = {
|
|
30798
31189
|
isRunning: (port) => probeCdp(port),
|
|
30799
31190
|
launch(chrome, args2) {
|
|
@@ -30834,9 +31225,11 @@ var init_browse_driver = __esm({
|
|
|
30834
31225
|
});
|
|
30835
31226
|
try {
|
|
30836
31227
|
const context = browser.contexts()[0] ?? await browser.newContext();
|
|
30837
|
-
const
|
|
30838
|
-
|
|
30839
|
-
|
|
31228
|
+
const { page: targetPage, pages } = selectTargetPage(
|
|
31229
|
+
context.pages(),
|
|
31230
|
+
pageIndex
|
|
31231
|
+
);
|
|
31232
|
+
const page = targetPage ?? await context.newPage();
|
|
30840
31233
|
const dialogs = [];
|
|
30841
31234
|
page.on("dialog", (dialog) => {
|
|
30842
31235
|
const type2 = dialog.type();
|
|
@@ -30869,15 +31262,28 @@ var init_browse_driver = __esm({
|
|
|
30869
31262
|
}
|
|
30870
31263
|
},
|
|
30871
31264
|
commandLine: (pid) => readProcessCommandLine(pid),
|
|
30872
|
-
kill(pid) {
|
|
31265
|
+
kill(pid, signal = "SIGTERM") {
|
|
30873
31266
|
try {
|
|
30874
|
-
if (process.platform
|
|
30875
|
-
|
|
30876
|
-
|
|
30877
|
-
|
|
31267
|
+
if (process.platform === "win32") {
|
|
31268
|
+
if (signal === "SIGKILL") {
|
|
31269
|
+
try {
|
|
31270
|
+
execFileSync("taskkill", ["/PID", String(pid), "/T", "/F"], {
|
|
31271
|
+
stdio: "ignore",
|
|
31272
|
+
timeout: 3e3,
|
|
31273
|
+
windowsHide: true
|
|
31274
|
+
});
|
|
31275
|
+
return;
|
|
31276
|
+
} catch {
|
|
31277
|
+
}
|
|
30878
31278
|
}
|
|
31279
|
+
process.kill(pid, signal);
|
|
31280
|
+
return;
|
|
31281
|
+
}
|
|
31282
|
+
try {
|
|
31283
|
+
process.kill(-pid, signal);
|
|
31284
|
+
} catch {
|
|
30879
31285
|
}
|
|
30880
|
-
process.kill(pid,
|
|
31286
|
+
process.kill(pid, signal);
|
|
30881
31287
|
} catch {
|
|
30882
31288
|
}
|
|
30883
31289
|
}
|
|
@@ -30940,7 +31346,7 @@ __export(step_exports, {
|
|
|
30940
31346
|
recordStep: () => recordStep,
|
|
30941
31347
|
stepCommand: () => stepCommand
|
|
30942
31348
|
});
|
|
30943
|
-
import { copyFile, mkdir as mkdir5, stat as stat2 } from "fs/promises";
|
|
31349
|
+
import { copyFile, mkdir as mkdir5, rename, stat as stat2 } from "fs/promises";
|
|
30944
31350
|
import path56 from "path";
|
|
30945
31351
|
import chalk29 from "chalk";
|
|
30946
31352
|
function parseMaxSteps(val) {
|
|
@@ -30975,11 +31381,18 @@ async function recordStep(input) {
|
|
|
30975
31381
|
let finalScreenshotPath = null;
|
|
30976
31382
|
if (shotSource) {
|
|
30977
31383
|
const destName = `step-${stepNumber2}.png`;
|
|
30978
|
-
|
|
30979
|
-
|
|
30980
|
-
|
|
31384
|
+
const destPath = path56.join(screenshotsDir, destName);
|
|
31385
|
+
finalScreenshotPath = destPath;
|
|
31386
|
+
if (path56.resolve(destPath) !== shotSource) {
|
|
31387
|
+
if (input.moveShot) {
|
|
31388
|
+
await rename(shotSource, destPath).catch(
|
|
31389
|
+
() => copyFile(shotSource, destPath)
|
|
31390
|
+
);
|
|
31391
|
+
} else {
|
|
31392
|
+
await copyFile(shotSource, destPath);
|
|
31393
|
+
}
|
|
30981
31394
|
}
|
|
30982
|
-
screenshotRef = path56.join("screenshots", destName);
|
|
31395
|
+
screenshotRef = path56.posix.join("screenshots", destName);
|
|
30983
31396
|
}
|
|
30984
31397
|
const parsedStep = runStepSchema.parse({
|
|
30985
31398
|
step_number: stepNumber2,
|
|
@@ -31072,7 +31485,7 @@ async function stepCommand(runArg, opts) {
|
|
|
31072
31485
|
{
|
|
31073
31486
|
step_number: res.step.step_number,
|
|
31074
31487
|
cancelled: true,
|
|
31075
|
-
path: path56.relative(projectRoot, res.stepPath),
|
|
31488
|
+
path: path56.relative(projectRoot, res.stepPath).split(path56.sep).join("/"),
|
|
31076
31489
|
action: res.step.action,
|
|
31077
31490
|
note: res.step.note,
|
|
31078
31491
|
url: res.step.url,
|
|
@@ -31100,7 +31513,7 @@ async function stepCommand(runArg, opts) {
|
|
|
31100
31513
|
JSON.stringify(
|
|
31101
31514
|
{
|
|
31102
31515
|
step_number: res.step.step_number,
|
|
31103
|
-
path: path56.relative(projectRoot, res.stepPath),
|
|
31516
|
+
path: path56.relative(projectRoot, res.stepPath).split(path56.sep).join("/"),
|
|
31104
31517
|
action: res.step.action,
|
|
31105
31518
|
note: res.step.note,
|
|
31106
31519
|
url: res.step.url,
|
|
@@ -31174,8 +31587,10 @@ __export(browse_exports, {
|
|
|
31174
31587
|
detectAntiBotChallenge: () => detectAntiBotChallenge,
|
|
31175
31588
|
findChrome: () => findChrome,
|
|
31176
31589
|
isHeadlessEnvironment: () => isHeadlessEnvironment,
|
|
31590
|
+
parseActionCommand: () => parseActionCommand,
|
|
31177
31591
|
parseViewport: () => parseViewport,
|
|
31178
31592
|
readBrowseState: () => readBrowseState,
|
|
31593
|
+
resilientAriaSnapshot: () => resilientAriaSnapshot,
|
|
31179
31594
|
shouldDisableSandbox: () => shouldDisableSandbox,
|
|
31180
31595
|
start: () => start,
|
|
31181
31596
|
waitForVisualStability: () => waitForVisualStability,
|
|
@@ -31240,7 +31655,10 @@ function findChrome(env = process.env, platform2 = process.platform, exists = ex
|
|
|
31240
31655
|
] : platform2 === "win32" ? [
|
|
31241
31656
|
`${env.PROGRAMFILES ?? "C:\\Program Files"}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
31242
31657
|
`${env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)"}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
31243
|
-
`${env.LOCALAPPDATA ?? ""}\\Google\\Chrome\\Application\\chrome.exe
|
|
31658
|
+
`${env.LOCALAPPDATA ?? ""}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
31659
|
+
`${env.PROGRAMFILES ?? "C:\\Program Files"}\\Microsoft\\Edge\\Application\\msedge.exe`,
|
|
31660
|
+
`${env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)"}\\Microsoft\\Edge\\Application\\msedge.exe`,
|
|
31661
|
+
`${env.LOCALAPPDATA ?? ""}\\Microsoft\\Edge\\Application\\msedge.exe`
|
|
31244
31662
|
] : [
|
|
31245
31663
|
"/usr/bin/google-chrome",
|
|
31246
31664
|
"/usr/bin/google-chrome-stable",
|
|
@@ -31429,7 +31847,7 @@ function checkAndRecordStagnation(projectRoot, runId, fingerprint, action) {
|
|
|
31429
31847
|
}
|
|
31430
31848
|
return { count };
|
|
31431
31849
|
}
|
|
31432
|
-
async function waitForVisualStability(page, maxWaitMs =
|
|
31850
|
+
async function waitForVisualStability(page, maxWaitMs = 150) {
|
|
31433
31851
|
let timer;
|
|
31434
31852
|
try {
|
|
31435
31853
|
const nodeTimeout = new Promise((resolve3) => {
|
|
@@ -31531,7 +31949,7 @@ async function detectAntiBotChallenge(page, allowEvaluate = true) {
|
|
|
31531
31949
|
}
|
|
31532
31950
|
return void 0;
|
|
31533
31951
|
}
|
|
31534
|
-
async function withPage(opts, driver, fn, target) {
|
|
31952
|
+
async function withPage(opts, driver, fn, target, onPostShot) {
|
|
31535
31953
|
const state = readBrowseState();
|
|
31536
31954
|
if (!state) {
|
|
31537
31955
|
fail(NO_BROWSER, opts);
|
|
@@ -31541,23 +31959,53 @@ async function withPage(opts, driver, fn, target) {
|
|
|
31541
31959
|
try {
|
|
31542
31960
|
attached = state.activeTabIndex !== void 0 ? await driver.attach(state.port, state.activeTabIndex) : await driver.attach(state.port);
|
|
31543
31961
|
} catch {
|
|
31544
|
-
|
|
31545
|
-
|
|
31962
|
+
const chrome = findChrome();
|
|
31963
|
+
if (!chrome) {
|
|
31964
|
+
fail(NO_BROWSER, opts);
|
|
31965
|
+
return null;
|
|
31966
|
+
}
|
|
31967
|
+
try {
|
|
31968
|
+
if (state.pid) {
|
|
31969
|
+
await reapBrowser(state.pid, profileDir(), driver);
|
|
31970
|
+
}
|
|
31971
|
+
const headless = state.headless ?? isHeadlessEnvironment(opts);
|
|
31972
|
+
const pid = driver.launch(chrome, chromeArgs(opts, state.port, headless));
|
|
31973
|
+
let running = false;
|
|
31974
|
+
for (let i = 0; i < 25; i++) {
|
|
31975
|
+
if (await driver.isRunning(state.port)) {
|
|
31976
|
+
running = true;
|
|
31977
|
+
break;
|
|
31978
|
+
}
|
|
31979
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
31980
|
+
}
|
|
31981
|
+
if (!running) {
|
|
31982
|
+
fail(NO_BROWSER, opts);
|
|
31983
|
+
return null;
|
|
31984
|
+
}
|
|
31985
|
+
writeState({ ...state, pid, headless });
|
|
31986
|
+
attached = state.activeTabIndex !== void 0 ? await driver.attach(state.port, state.activeTabIndex) : await driver.attach(state.port);
|
|
31987
|
+
} catch {
|
|
31988
|
+
fail(NO_BROWSER, opts);
|
|
31989
|
+
return null;
|
|
31990
|
+
}
|
|
31546
31991
|
}
|
|
31547
31992
|
const { page } = attached;
|
|
31548
31993
|
try {
|
|
31549
31994
|
if (state.viewport) {
|
|
31550
|
-
|
|
31551
|
-
if (state.viewport.width
|
|
31552
|
-
|
|
31553
|
-
|
|
31554
|
-
|
|
31555
|
-
const
|
|
31556
|
-
|
|
31557
|
-
|
|
31558
|
-
|
|
31995
|
+
const currentVp = typeof page.viewportSize === "function" ? page.viewportSize() : null;
|
|
31996
|
+
if (!currentVp || currentVp.width !== state.viewport.width || currentVp.height !== state.viewport.height) {
|
|
31997
|
+
await page.setViewportSize(state.viewport);
|
|
31998
|
+
if (state.viewport.width <= 500 && typeof page.context === "function") {
|
|
31999
|
+
try {
|
|
32000
|
+
const context = page.context();
|
|
32001
|
+
if (context && typeof context.newCDPSession === "function") {
|
|
32002
|
+
const cdp = await context.newCDPSession(page);
|
|
32003
|
+
await cdp.send("Emulation.setTouchEmulationEnabled", {
|
|
32004
|
+
enabled: true
|
|
32005
|
+
});
|
|
32006
|
+
}
|
|
32007
|
+
} catch {
|
|
31559
32008
|
}
|
|
31560
|
-
} catch {
|
|
31561
32009
|
}
|
|
31562
32010
|
}
|
|
31563
32011
|
}
|
|
@@ -31582,9 +32030,12 @@ async function withPage(opts, driver, fn, target) {
|
|
|
31582
32030
|
}).catch(() => void 0);
|
|
31583
32031
|
}
|
|
31584
32032
|
}
|
|
32033
|
+
if (onPostShot) {
|
|
32034
|
+
await onPostShot(page);
|
|
32035
|
+
}
|
|
31585
32036
|
const dialogs = attached.dialogs ?? [];
|
|
31586
32037
|
const dialog = dialogs.length > 0 ? dialogs.join("; ") : void 0;
|
|
31587
|
-
const challenge = await detectAntiBotChallenge(page,
|
|
32038
|
+
const challenge = opts.shot ? await detectAntiBotChallenge(page, true) : void 0;
|
|
31588
32039
|
return {
|
|
31589
32040
|
url: page.url(),
|
|
31590
32041
|
title: await page.title(),
|
|
@@ -31624,70 +32075,89 @@ function reportPage(opts, result, extra = {}) {
|
|
|
31624
32075
|
human
|
|
31625
32076
|
);
|
|
31626
32077
|
}
|
|
32078
|
+
async function resilientAriaSnapshot(page) {
|
|
32079
|
+
try {
|
|
32080
|
+
return await page.locator("body").ariaSnapshot({ timeout: 5e3 }) ?? "";
|
|
32081
|
+
} catch (originalErr) {
|
|
32082
|
+
try {
|
|
32083
|
+
return await page.locator(":root").ariaSnapshot({ timeout: 3e3 }) ?? "";
|
|
32084
|
+
} catch {
|
|
32085
|
+
if (page.url() === "about:blank") {
|
|
32086
|
+
return "";
|
|
32087
|
+
}
|
|
32088
|
+
throw originalErr;
|
|
32089
|
+
}
|
|
32090
|
+
}
|
|
32091
|
+
}
|
|
32092
|
+
function formatIndexedElementLine(el) {
|
|
32093
|
+
return ` [#${el.index}] ${el.role}${el.name ? ` "${el.name}"` : ""}${el.checked ? " [checked]" : ""}${el.disabled ? " [disabled]" : ""}`;
|
|
32094
|
+
}
|
|
32095
|
+
async function captureSnapshotWithFrames(p, includeIndexed) {
|
|
32096
|
+
let snapshot = await resilientAriaSnapshot(p);
|
|
32097
|
+
if (typeof p.frames === "function") {
|
|
32098
|
+
const childFrames = p.frames().filter((f) => {
|
|
32099
|
+
try {
|
|
32100
|
+
return f !== (typeof p.mainFrame === "function" ? p.mainFrame() : null);
|
|
32101
|
+
} catch {
|
|
32102
|
+
return false;
|
|
32103
|
+
}
|
|
32104
|
+
});
|
|
32105
|
+
if (childFrames.length > 0) {
|
|
32106
|
+
const frameSnapshots = [];
|
|
32107
|
+
for (let i = 0; i < childFrames.length; i++) {
|
|
32108
|
+
const f = childFrames[i];
|
|
32109
|
+
const frameUrl = typeof f.url === "function" ? f.url() : "";
|
|
32110
|
+
if (!frameUrl || frameUrl === "about:blank") continue;
|
|
32111
|
+
const frameName = (typeof f.name === "function" ? f.name() : "") || `frame-${i + 1}`;
|
|
32112
|
+
try {
|
|
32113
|
+
const snap = await f.locator("body").ariaSnapshot({ timeout: 1500 });
|
|
32114
|
+
if (snap && snap.trim()) {
|
|
32115
|
+
frameSnapshots.push(
|
|
32116
|
+
`[Iframe: ${frameName} (${frameUrl})]
|
|
32117
|
+
${snap.trim()}`
|
|
32118
|
+
);
|
|
32119
|
+
}
|
|
32120
|
+
} catch {
|
|
32121
|
+
}
|
|
32122
|
+
}
|
|
32123
|
+
if (frameSnapshots.length > 0) {
|
|
32124
|
+
snapshot = `${snapshot}
|
|
32125
|
+
|
|
32126
|
+
${frameSnapshots.join("\n\n")}`;
|
|
32127
|
+
}
|
|
32128
|
+
}
|
|
32129
|
+
}
|
|
32130
|
+
let elements = [];
|
|
32131
|
+
if (includeIndexed) {
|
|
32132
|
+
elements = await markAndQueryIndexedElements(p);
|
|
32133
|
+
}
|
|
32134
|
+
const formatted = includeIndexed ? [
|
|
32135
|
+
"Interactive elements:",
|
|
32136
|
+
...elements.map(formatIndexedElementLine),
|
|
32137
|
+
"",
|
|
32138
|
+
"Accessibility snapshot:",
|
|
32139
|
+
snapshot
|
|
32140
|
+
].join("\n") : snapshot;
|
|
32141
|
+
return { snapshot, elements, formatted };
|
|
32142
|
+
}
|
|
31627
32143
|
async function pageCommand(command, args2, opts, driver) {
|
|
31628
32144
|
if (command === "snapshot") {
|
|
31629
|
-
let
|
|
31630
|
-
let elements = [];
|
|
32145
|
+
let captured;
|
|
31631
32146
|
const result2 = await withPage(
|
|
31632
32147
|
{ ...opts, shot: void 0 },
|
|
31633
32148
|
driver,
|
|
31634
32149
|
async (p) => {
|
|
31635
|
-
|
|
31636
|
-
if (typeof p.frames === "function") {
|
|
31637
|
-
const childFrames = p.frames().filter((f) => {
|
|
31638
|
-
try {
|
|
31639
|
-
return f !== (typeof p.mainFrame === "function" ? p.mainFrame() : null);
|
|
31640
|
-
} catch {
|
|
31641
|
-
return false;
|
|
31642
|
-
}
|
|
31643
|
-
});
|
|
31644
|
-
if (childFrames.length > 0) {
|
|
31645
|
-
const frameSnapshots = [];
|
|
31646
|
-
for (let i = 0; i < childFrames.length; i++) {
|
|
31647
|
-
const f = childFrames[i];
|
|
31648
|
-
const frameUrl = typeof f.url === "function" ? f.url() : "";
|
|
31649
|
-
if (!frameUrl || frameUrl === "about:blank") continue;
|
|
31650
|
-
const frameName = (typeof f.name === "function" ? f.name() : "") || `frame-${i + 1}`;
|
|
31651
|
-
try {
|
|
31652
|
-
const snap = await f.locator("body").ariaSnapshot({ timeout: 1500 });
|
|
31653
|
-
if (snap && snap.trim()) {
|
|
31654
|
-
frameSnapshots.push(
|
|
31655
|
-
`[Iframe: ${frameName} (${frameUrl})]
|
|
31656
|
-
${snap.trim()}`
|
|
31657
|
-
);
|
|
31658
|
-
}
|
|
31659
|
-
} catch {
|
|
31660
|
-
}
|
|
31661
|
-
}
|
|
31662
|
-
if (frameSnapshots.length > 0) {
|
|
31663
|
-
snapshot = `${snapshot}
|
|
31664
|
-
|
|
31665
|
-
${frameSnapshots.join("\n\n")}`;
|
|
31666
|
-
}
|
|
31667
|
-
}
|
|
31668
|
-
}
|
|
31669
|
-
if (opts.indexed) {
|
|
31670
|
-
elements = await markAndQueryIndexedElements(p);
|
|
31671
|
-
}
|
|
32150
|
+
captured = await captureSnapshotWithFrames(p, Boolean(opts.indexed));
|
|
31672
32151
|
}
|
|
31673
32152
|
);
|
|
31674
|
-
if (result2) {
|
|
32153
|
+
if (result2 && captured) {
|
|
31675
32154
|
if (opts.indexed) {
|
|
31676
|
-
const lines = [
|
|
31677
|
-
"Interactive elements:",
|
|
31678
|
-
...elements.map(
|
|
31679
|
-
(el) => ` [#${el.index}] ${el.role}${el.name ? ` "${el.name}"` : ""}${el.disabled ? " [disabled]" : ""}`
|
|
31680
|
-
),
|
|
31681
|
-
"",
|
|
31682
|
-
"Accessibility snapshot:",
|
|
31683
|
-
snapshot
|
|
31684
|
-
];
|
|
31685
32155
|
reportPage(opts, result2, {
|
|
31686
|
-
snapshot:
|
|
31687
|
-
interactive_elements: elements
|
|
32156
|
+
snapshot: captured.formatted,
|
|
32157
|
+
interactive_elements: captured.elements
|
|
31688
32158
|
});
|
|
31689
32159
|
} else {
|
|
31690
|
-
reportPage(opts, result2, { snapshot });
|
|
32160
|
+
reportPage(opts, result2, { snapshot: captured.snapshot });
|
|
31691
32161
|
}
|
|
31692
32162
|
}
|
|
31693
32163
|
return;
|
|
@@ -31721,30 +32191,54 @@ ${frameSnapshots.join("\n\n")}`;
|
|
|
31721
32191
|
if (args2.length < action.minArgs) {
|
|
31722
32192
|
return fail(`Usage: iamu browse ${action.usage}`, opts);
|
|
31723
32193
|
}
|
|
32194
|
+
let gotoSnapshot;
|
|
31724
32195
|
const result = await withPage(
|
|
31725
32196
|
opts,
|
|
31726
32197
|
driver,
|
|
31727
32198
|
(p) => action.run(p, args2, opts),
|
|
31728
|
-
action.target?.(args2)
|
|
32199
|
+
action.target?.(args2),
|
|
32200
|
+
command === "goto" && opts.snapshot !== false ? async (p) => {
|
|
32201
|
+
if (typeof p.locator === "function") {
|
|
32202
|
+
gotoSnapshot = await captureSnapshotWithFrames(p, true).catch(
|
|
32203
|
+
() => void 0
|
|
32204
|
+
);
|
|
32205
|
+
}
|
|
32206
|
+
} : void 0
|
|
31729
32207
|
);
|
|
31730
|
-
if (result)
|
|
32208
|
+
if (result) {
|
|
32209
|
+
const snap = gotoSnapshot;
|
|
32210
|
+
const hasSnapshot = snap && (snap.snapshot.length > 0 || snap.elements.length > 0);
|
|
32211
|
+
reportPage(
|
|
32212
|
+
opts,
|
|
32213
|
+
result,
|
|
32214
|
+
hasSnapshot ? {
|
|
32215
|
+
snapshot: snap.formatted,
|
|
32216
|
+
interactive_elements: snap.elements
|
|
32217
|
+
} : {}
|
|
32218
|
+
);
|
|
32219
|
+
}
|
|
31731
32220
|
}
|
|
31732
|
-
function printStep(opts, record, projectRoot, stagnationWarning, challengeWarning) {
|
|
32221
|
+
function printStep(opts, record, projectRoot, stagnationWarning, challengeWarning, stepSnapshot) {
|
|
31733
32222
|
const { step } = record;
|
|
31734
32223
|
const warnings = [challengeWarning, stagnationWarning].filter(
|
|
31735
32224
|
Boolean
|
|
31736
32225
|
);
|
|
32226
|
+
const hasSnapshot = stepSnapshot && (stepSnapshot.snapshot.length > 0 || stepSnapshot.elements.length > 0);
|
|
31737
32227
|
if (opts.json) {
|
|
31738
32228
|
const doc = {
|
|
31739
32229
|
step_number: step.step_number,
|
|
31740
|
-
path: path57.relative(projectRoot, record.stepPath),
|
|
32230
|
+
path: path57.relative(projectRoot, record.stepPath).split(path57.sep).join("/"),
|
|
31741
32231
|
action: step.action,
|
|
31742
32232
|
note: step.note,
|
|
31743
32233
|
url: step.url,
|
|
31744
32234
|
screenshot: step.screenshot,
|
|
31745
32235
|
surface: step.surface,
|
|
31746
32236
|
...warnings.length > 0 ? { warning: warnings.join(" | ") } : {},
|
|
31747
|
-
...record.cancelled ? { cancelled: true } : {}
|
|
32237
|
+
...record.cancelled ? { cancelled: true } : {},
|
|
32238
|
+
...hasSnapshot ? {
|
|
32239
|
+
snapshot: stepSnapshot.formatted,
|
|
32240
|
+
interactive_elements: stepSnapshot.elements
|
|
32241
|
+
} : {}
|
|
31748
32242
|
};
|
|
31749
32243
|
console.log(JSON.stringify(doc, null, 2));
|
|
31750
32244
|
return;
|
|
@@ -31778,6 +32272,9 @@ function printStep(opts, record, projectRoot, stagnationWarning, challengeWarnin
|
|
|
31778
32272
|
console.log(` ${symbols.warning} ${chalk30.yellow(w)}`);
|
|
31779
32273
|
}
|
|
31780
32274
|
}
|
|
32275
|
+
if (hasSnapshot) {
|
|
32276
|
+
console.log(stepSnapshot.formatted);
|
|
32277
|
+
}
|
|
31781
32278
|
}
|
|
31782
32279
|
async function clearOriginStorage(origin, driver = cdpDriver, returnTo) {
|
|
31783
32280
|
const state = readBrowseState();
|
|
@@ -31868,6 +32365,45 @@ async function clearStorage(opts, driver) {
|
|
|
31868
32365
|
` opened ${returnTo}`
|
|
31869
32366
|
]);
|
|
31870
32367
|
}
|
|
32368
|
+
function parseActionCommand(raw) {
|
|
32369
|
+
const tokens = [];
|
|
32370
|
+
let current = "";
|
|
32371
|
+
let quote = null;
|
|
32372
|
+
let hasToken = false;
|
|
32373
|
+
for (let i = 0; i < raw.length; i++) {
|
|
32374
|
+
const ch = raw[i];
|
|
32375
|
+
if (ch === "\\" && i + 1 < raw.length && quote !== "'") {
|
|
32376
|
+
current += raw[++i];
|
|
32377
|
+
hasToken = true;
|
|
32378
|
+
continue;
|
|
32379
|
+
}
|
|
32380
|
+
if (quote) {
|
|
32381
|
+
if (ch === quote) {
|
|
32382
|
+
quote = null;
|
|
32383
|
+
} else {
|
|
32384
|
+
current += ch;
|
|
32385
|
+
}
|
|
32386
|
+
continue;
|
|
32387
|
+
}
|
|
32388
|
+
if (ch === '"' || ch === "'") {
|
|
32389
|
+
quote = ch;
|
|
32390
|
+
hasToken = true;
|
|
32391
|
+
continue;
|
|
32392
|
+
}
|
|
32393
|
+
if (/\s/.test(ch)) {
|
|
32394
|
+
if (hasToken) {
|
|
32395
|
+
tokens.push(current);
|
|
32396
|
+
current = "";
|
|
32397
|
+
hasToken = false;
|
|
32398
|
+
}
|
|
32399
|
+
continue;
|
|
32400
|
+
}
|
|
32401
|
+
current += ch;
|
|
32402
|
+
hasToken = true;
|
|
32403
|
+
}
|
|
32404
|
+
if (hasToken) tokens.push(current);
|
|
32405
|
+
return tokens;
|
|
32406
|
+
}
|
|
31871
32407
|
async function browseStep(args2, opts, driver) {
|
|
31872
32408
|
if (opts.stepFile) {
|
|
31873
32409
|
try {
|
|
@@ -31888,9 +32424,17 @@ async function browseStep(args2, opts, driver) {
|
|
|
31888
32424
|
if (Array.isArray(data.cmd)) {
|
|
31889
32425
|
args2 = data.cmd.map(String);
|
|
31890
32426
|
} else if (typeof data.cmd === "string") {
|
|
31891
|
-
args2 = data.cmd.trim()
|
|
32427
|
+
args2 = parseActionCommand(data.cmd.trim());
|
|
31892
32428
|
}
|
|
31893
32429
|
}
|
|
32430
|
+
if (Array.isArray(data.then)) {
|
|
32431
|
+
const extra = data.then.map(
|
|
32432
|
+
(item) => Array.isArray(item) ? item.map(
|
|
32433
|
+
(tok) => /[\s"']/.test(String(tok)) ? JSON.stringify(String(tok)) : String(tok)
|
|
32434
|
+
).join(" ") : String(item)
|
|
32435
|
+
);
|
|
32436
|
+
opts.then = [...opts.then ?? [], ...extra];
|
|
32437
|
+
}
|
|
31894
32438
|
} catch (err) {
|
|
31895
32439
|
return fail(
|
|
31896
32440
|
`Failed to read step payload from ${opts.stepFile}: ${err.message}`,
|
|
@@ -31926,6 +32470,29 @@ async function browseStep(args2, opts, driver) {
|
|
|
31926
32470
|
opts
|
|
31927
32471
|
);
|
|
31928
32472
|
}
|
|
32473
|
+
const chainedActions = [];
|
|
32474
|
+
if (opts.then && opts.then.length > 0) {
|
|
32475
|
+
for (const rawCmd of opts.then) {
|
|
32476
|
+
const tokens = parseActionCommand(rawCmd.trim());
|
|
32477
|
+
if (tokens.length === 0) continue;
|
|
32478
|
+
const subName = tokens[0];
|
|
32479
|
+
const subArgs = tokens.slice(1);
|
|
32480
|
+
const subAction = findPageAction(subName);
|
|
32481
|
+
if (!subAction) {
|
|
32482
|
+
return fail(
|
|
32483
|
+
`Unknown chained browse action "${subName}" in --then. Supported: ${Object.keys(PAGE_ACTIONS).join(", ")}.`,
|
|
32484
|
+
opts
|
|
32485
|
+
);
|
|
32486
|
+
}
|
|
32487
|
+
if (subArgs.length < subAction.minArgs) {
|
|
32488
|
+
return fail(
|
|
32489
|
+
`Usage for chained --then action: ${subAction.usage}`,
|
|
32490
|
+
opts
|
|
32491
|
+
);
|
|
32492
|
+
}
|
|
32493
|
+
chainedActions.push({ action: subAction, args: subArgs });
|
|
32494
|
+
}
|
|
32495
|
+
}
|
|
31929
32496
|
const runDirPath = resolveRunDir(ctx.projectRoot, opts.run);
|
|
31930
32497
|
if (!runDirPath) return;
|
|
31931
32498
|
const maxSteps = parseMaxSteps(opts.maxSteps);
|
|
@@ -31937,12 +32504,25 @@ async function browseStep(args2, opts, driver) {
|
|
|
31937
32504
|
"screenshots",
|
|
31938
32505
|
`.pending-${randomUUID11()}.png`
|
|
31939
32506
|
);
|
|
32507
|
+
let stepSnapshot;
|
|
31940
32508
|
try {
|
|
31941
32509
|
const page = await withPage(
|
|
31942
32510
|
{ ...opts, shot: pendingShot },
|
|
31943
32511
|
driver,
|
|
31944
|
-
(p) =>
|
|
31945
|
-
|
|
32512
|
+
async (p) => {
|
|
32513
|
+
await action.run(p, actionArgs, opts);
|
|
32514
|
+
for (const sub of chainedActions) {
|
|
32515
|
+
await sub.action.run(p, sub.args, opts);
|
|
32516
|
+
}
|
|
32517
|
+
},
|
|
32518
|
+
action.target?.(actionArgs),
|
|
32519
|
+
opts.snapshot !== false ? async (p) => {
|
|
32520
|
+
if (typeof p.locator === "function") {
|
|
32521
|
+
stepSnapshot = await captureSnapshotWithFrames(p, true).catch(
|
|
32522
|
+
() => void 0
|
|
32523
|
+
);
|
|
32524
|
+
}
|
|
32525
|
+
} : void 0
|
|
31946
32526
|
);
|
|
31947
32527
|
if (!page) return;
|
|
31948
32528
|
let stagnationWarning;
|
|
@@ -31977,6 +32557,7 @@ async function browseStep(args2, opts, driver) {
|
|
|
31977
32557
|
action: opts.action,
|
|
31978
32558
|
note: effectiveNote,
|
|
31979
32559
|
shot: page.screenshot,
|
|
32560
|
+
moveShot: true,
|
|
31980
32561
|
url: page.url,
|
|
31981
32562
|
surface: opts.surface ?? page.title ?? null,
|
|
31982
32563
|
maxSteps
|
|
@@ -31986,7 +32567,8 @@ async function browseStep(args2, opts, driver) {
|
|
|
31986
32567
|
record,
|
|
31987
32568
|
ctx.projectRoot,
|
|
31988
32569
|
stagnationWarning,
|
|
31989
|
-
challengeWarning
|
|
32570
|
+
challengeWarning,
|
|
32571
|
+
stepSnapshot
|
|
31990
32572
|
);
|
|
31991
32573
|
if (record.cancelled) {
|
|
31992
32574
|
if (!opts.json) {
|
|
@@ -32791,6 +33373,14 @@ import { spawnSync } from "child_process";
|
|
|
32791
33373
|
import { existsSync as existsSync18, promises as fs61 } from "fs";
|
|
32792
33374
|
import path58 from "path";
|
|
32793
33375
|
import chalk32 from "chalk";
|
|
33376
|
+
function detectPackageManager(targetDir) {
|
|
33377
|
+
if (existsSync18(path58.join(targetDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
33378
|
+
if (existsSync18(path58.join(targetDir, "yarn.lock"))) return "yarn";
|
|
33379
|
+
if (existsSync18(path58.join(targetDir, "bun.lockb")) || existsSync18(path58.join(targetDir, "bun.lock"))) {
|
|
33380
|
+
return "bun";
|
|
33381
|
+
}
|
|
33382
|
+
return "npm";
|
|
33383
|
+
}
|
|
32794
33384
|
async function detectPreflightCommand(targetDir) {
|
|
32795
33385
|
const pkgPath = path58.join(targetDir, "package.json");
|
|
32796
33386
|
if (existsSync18(pkgPath)) {
|
|
@@ -32799,10 +33389,8 @@ async function detectPreflightCommand(targetDir) {
|
|
|
32799
33389
|
const pkg = JSON.parse(raw);
|
|
32800
33390
|
const scripts = pkg.scripts || {};
|
|
32801
33391
|
if (scripts.typecheck) {
|
|
32802
|
-
|
|
32803
|
-
|
|
32804
|
-
if (scripts.check) {
|
|
32805
|
-
return { command: "npm", args: ["run", "check"] };
|
|
33392
|
+
const pm = detectPackageManager(targetDir);
|
|
33393
|
+
return { command: pm, args: ["run", "typecheck"] };
|
|
32806
33394
|
}
|
|
32807
33395
|
} catch {
|
|
32808
33396
|
}
|
|
@@ -32811,11 +33399,6 @@ async function detectPreflightCommand(targetDir) {
|
|
|
32811
33399
|
if (existsSync18(tsconfigPath)) {
|
|
32812
33400
|
return { command: "npx", args: ["--no-install", "tsc", "--noEmit"] };
|
|
32813
33401
|
}
|
|
32814
|
-
const biomePath = path58.join(targetDir, "biome.json");
|
|
32815
|
-
const biomecPath = path58.join(targetDir, "biome.jsonc");
|
|
32816
|
-
if (existsSync18(biomePath) || existsSync18(biomecPath)) {
|
|
32817
|
-
return { command: "npx", args: ["--no-install", "biome", "check"] };
|
|
32818
|
-
}
|
|
32819
33402
|
return null;
|
|
32820
33403
|
}
|
|
32821
33404
|
async function runPreflightCheck(targetDir) {
|
|
@@ -32824,7 +33407,7 @@ async function runPreflightCheck(targetDir) {
|
|
|
32824
33407
|
return {
|
|
32825
33408
|
ok: true,
|
|
32826
33409
|
skipped: true,
|
|
32827
|
-
reason: "No typecheck or
|
|
33410
|
+
reason: "No typecheck script or tsconfig.json detected in project"
|
|
32828
33411
|
};
|
|
32829
33412
|
}
|
|
32830
33413
|
const cmdString = `${detected.command} ${detected.args.join(" ")}`;
|
|
@@ -32832,20 +33415,22 @@ async function runPreflightCheck(targetDir) {
|
|
|
32832
33415
|
cwd: targetDir,
|
|
32833
33416
|
encoding: "utf8",
|
|
32834
33417
|
timeout: 3e4,
|
|
32835
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
33418
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
33419
|
+
shell: process.platform === "win32"
|
|
32836
33420
|
});
|
|
32837
|
-
const
|
|
32838
|
-
|
|
33421
|
+
const combined = ((proc.stdout || "") + (proc.stderr || "")).trim();
|
|
33422
|
+
const output = combined || (proc.error ? proc.error.message : "") || `Process exited with code ${proc.status ?? 1}`;
|
|
33423
|
+
if (proc.status === 0 && !proc.error) {
|
|
32839
33424
|
return {
|
|
32840
33425
|
ok: true,
|
|
32841
33426
|
command: cmdString,
|
|
32842
|
-
output:
|
|
33427
|
+
output: combined
|
|
32843
33428
|
};
|
|
32844
33429
|
}
|
|
32845
33430
|
return {
|
|
32846
33431
|
ok: false,
|
|
32847
33432
|
command: cmdString,
|
|
32848
|
-
output
|
|
33433
|
+
output
|
|
32849
33434
|
};
|
|
32850
33435
|
}
|
|
32851
33436
|
async function preflight(opts = {}) {
|
|
@@ -32861,7 +33446,7 @@ async function preflight(opts = {}) {
|
|
|
32861
33446
|
skipped: !!result.skipped,
|
|
32862
33447
|
command: result.command ?? null,
|
|
32863
33448
|
reason: result.reason ?? null,
|
|
32864
|
-
message: result.skipped ? "No typecheck
|
|
33449
|
+
message: result.skipped ? "No typecheck script detected; preflight skipped" : "Preflight check passed with 0 errors"
|
|
32865
33450
|
},
|
|
32866
33451
|
null,
|
|
32867
33452
|
2
|
|
@@ -32871,7 +33456,7 @@ async function preflight(opts = {}) {
|
|
|
32871
33456
|
}
|
|
32872
33457
|
if (result.skipped) {
|
|
32873
33458
|
console.log(
|
|
32874
|
-
chalk32.dim("\u25C7 Preflight skipped: no typecheck
|
|
33459
|
+
chalk32.dim("\u25C7 Preflight skipped: no typecheck script detected")
|
|
32875
33460
|
);
|
|
32876
33461
|
} else {
|
|
32877
33462
|
console.log(
|
|
@@ -32897,9 +33482,7 @@ ${result.output}`,
|
|
|
32897
33482
|
console.error(chalk32.dim(result.output));
|
|
32898
33483
|
}
|
|
32899
33484
|
console.error(
|
|
32900
|
-
chalk32.yellow(
|
|
32901
|
-
"\nFix compilation/lint errors before starting a walkthrough."
|
|
32902
|
-
)
|
|
33485
|
+
chalk32.yellow("\nFix compilation errors before starting a walkthrough.")
|
|
32903
33486
|
);
|
|
32904
33487
|
process.exit(1);
|
|
32905
33488
|
}
|
|
@@ -32911,7 +33494,7 @@ var init_preflight = __esm({
|
|
|
32911
33494
|
});
|
|
32912
33495
|
|
|
32913
33496
|
// src/worktree-state.ts
|
|
32914
|
-
import { execFileSync } from "child_process";
|
|
33497
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
32915
33498
|
import { createHash as createHash6 } from "crypto";
|
|
32916
33499
|
import {
|
|
32917
33500
|
closeSync as closeSync3,
|
|
@@ -32923,7 +33506,7 @@ import {
|
|
|
32923
33506
|
} from "fs";
|
|
32924
33507
|
import path59 from "path";
|
|
32925
33508
|
function git(args2, cwd) {
|
|
32926
|
-
return
|
|
33509
|
+
return execFileSync2("git", args2, {
|
|
32927
33510
|
cwd,
|
|
32928
33511
|
stdio: ["ignore", "pipe", "ignore"],
|
|
32929
33512
|
timeout: GIT_TIMEOUT_MS2,
|
|
@@ -33056,7 +33639,7 @@ var init_worktree_state = __esm({
|
|
|
33056
33639
|
});
|
|
33057
33640
|
|
|
33058
33641
|
// src/build-info.ts
|
|
33059
|
-
import { execFileSync as
|
|
33642
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
33060
33643
|
import { readFileSync as readFileSync8 } from "fs";
|
|
33061
33644
|
function nonEmpty(value) {
|
|
33062
33645
|
const v = value?.trim();
|
|
@@ -33155,7 +33738,7 @@ var init_build_info = __esm({
|
|
|
33155
33738
|
SHA_RE = /^[0-9a-f]{7,64}$/i;
|
|
33156
33739
|
runGit = (args2, cwd) => {
|
|
33157
33740
|
try {
|
|
33158
|
-
const out2 =
|
|
33741
|
+
const out2 = execFileSync3("git", args2, {
|
|
33159
33742
|
cwd,
|
|
33160
33743
|
encoding: "utf8",
|
|
33161
33744
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -33458,6 +34041,13 @@ async function listSingleType(projectRoot, type2, capabilityFilter, diffFilter,
|
|
|
33458
34041
|
{
|
|
33459
34042
|
header: "outline",
|
|
33460
34043
|
value: (e) => e.frontmatter.outline ?? ""
|
|
34044
|
+
},
|
|
34045
|
+
{
|
|
34046
|
+
header: "entry",
|
|
34047
|
+
value: (e) => {
|
|
34048
|
+
const sfm = e.frontmatter;
|
|
34049
|
+
return chalk33.dim(sfm.entry_path ?? "/");
|
|
34050
|
+
}
|
|
33461
34051
|
}
|
|
33462
34052
|
]);
|
|
33463
34053
|
break;
|
|
@@ -34431,6 +35021,9 @@ function applyChanges(type2, fm, opts) {
|
|
|
34431
35021
|
opts.untestableBoundaries ? splitList(opts.untestableBoundaries) : null
|
|
34432
35022
|
);
|
|
34433
35023
|
}
|
|
35024
|
+
if ((opts.target || opts.authStrategy || opts.access) && fm.status !== "verified") {
|
|
35025
|
+
fm.status = "verified";
|
|
35026
|
+
}
|
|
34434
35027
|
} else if (type2 === "context") {
|
|
34435
35028
|
if (opts.domain !== void 0) put("domain", opts.domain || null);
|
|
34436
35029
|
if (opts.industry !== void 0) put("industry", opts.industry || null);
|
|
@@ -34946,21 +35539,39 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
34946
35539
|
const ctx = await resolveProject(opts);
|
|
34947
35540
|
if (!ctx) return;
|
|
34948
35541
|
const { projectRoot } = ctx;
|
|
34949
|
-
|
|
35542
|
+
const isSpecial = type2 === "testability" || type2 === "context";
|
|
35543
|
+
if (!isSpecial && !ENTITY_TYPES.includes(type2)) {
|
|
34950
35544
|
commandError(
|
|
34951
|
-
`Unknown entity type "${type2}". Supported: ${
|
|
35545
|
+
`Unknown entity type "${type2}". Supported: ${EDITABLE_TYPES.join(", ")}.`,
|
|
34952
35546
|
{ ...opts, code: "unknown_entity_type", fix: "iamu list" }
|
|
34953
35547
|
);
|
|
34954
35548
|
return;
|
|
34955
35549
|
}
|
|
34956
|
-
|
|
34957
|
-
|
|
35550
|
+
if (!isSpecial && !slug) {
|
|
35551
|
+
commandError(`A slug is required when editing a ${type2}.`, {
|
|
35552
|
+
...opts,
|
|
35553
|
+
code: "missing_slug",
|
|
35554
|
+
fix: `iamu edit ${type2} <slug>`
|
|
35555
|
+
});
|
|
35556
|
+
return;
|
|
35557
|
+
}
|
|
35558
|
+
const effectiveSlug = isSpecial ? type2 : slug;
|
|
35559
|
+
const filePath = type2 === "testability" ? testabilityPath(projectRoot) : type2 === "context" ? contextPath(projectRoot) : path62.join(
|
|
35560
|
+
projectRoot,
|
|
35561
|
+
"iamu",
|
|
35562
|
+
ENTITY_DIRS[type2],
|
|
35563
|
+
`${effectiveSlug}.md`
|
|
35564
|
+
);
|
|
34958
35565
|
try {
|
|
34959
35566
|
await fs64.access(filePath);
|
|
34960
35567
|
} catch {
|
|
34961
35568
|
commandError(
|
|
34962
|
-
`${type2} "${
|
|
34963
|
-
{
|
|
35569
|
+
`${type2}${isSpecial ? "" : ` "${effectiveSlug}"`} not found at ${path62.relative(projectRoot, filePath)}.`,
|
|
35570
|
+
{
|
|
35571
|
+
...opts,
|
|
35572
|
+
code: "entity_not_found",
|
|
35573
|
+
fix: isSpecial ? "iamu init" : `iamu list ${type2}s`
|
|
35574
|
+
}
|
|
34964
35575
|
);
|
|
34965
35576
|
return;
|
|
34966
35577
|
}
|
|
@@ -34970,7 +35581,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
34970
35581
|
{
|
|
34971
35582
|
...opts,
|
|
34972
35583
|
code: "non_interactive_environment",
|
|
34973
|
-
fix: `iamu set ${type2} ${
|
|
35584
|
+
fix: isSpecial ? `iamu set ${type2}` : `iamu set ${type2} ${effectiveSlug}`
|
|
34974
35585
|
}
|
|
34975
35586
|
);
|
|
34976
35587
|
return;
|
|
@@ -34983,7 +35594,8 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
34983
35594
|
const editorArgs = parts.slice(1).map((a) => a.replace(/^["']|["']$/g, ""));
|
|
34984
35595
|
await new Promise((resolve3) => {
|
|
34985
35596
|
const child = spawn2(editorCmd, [...editorArgs, filePath], {
|
|
34986
|
-
stdio: "inherit"
|
|
35597
|
+
stdio: "inherit",
|
|
35598
|
+
shell: process.platform === "win32"
|
|
34987
35599
|
});
|
|
34988
35600
|
child.on("exit", (code) => {
|
|
34989
35601
|
if (code !== 0) {
|
|
@@ -35013,7 +35625,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
35013
35625
|
try {
|
|
35014
35626
|
const content = await fs64.readFile(filePath, "utf8");
|
|
35015
35627
|
const { frontmatter } = parseFrontmatter(content);
|
|
35016
|
-
const schema2 = schemaByType[type2];
|
|
35628
|
+
const schema2 = type2 === "testability" ? testabilityFrontmatterSchema : type2 === "context" ? contextFrontmatterSchema : schemaByType[type2];
|
|
35017
35629
|
const parsed = schema2.safeParse(frontmatter);
|
|
35018
35630
|
if (!parsed.success) {
|
|
35019
35631
|
if (opts.json) {
|
|
@@ -35021,7 +35633,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
35021
35633
|
JSON.stringify({
|
|
35022
35634
|
valid: false,
|
|
35023
35635
|
type: type2,
|
|
35024
|
-
slug,
|
|
35636
|
+
slug: effectiveSlug,
|
|
35025
35637
|
path: filePath,
|
|
35026
35638
|
errors: parsed.error.issues.map((i) => ({
|
|
35027
35639
|
field: i.path.join(".") || "root",
|
|
@@ -35032,7 +35644,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
35032
35644
|
} else {
|
|
35033
35645
|
console.log();
|
|
35034
35646
|
console.log(
|
|
35035
|
-
` ${symbols.warning} ${chalk38.yellow(`Validation failed for ${type2} "${
|
|
35647
|
+
` ${symbols.warning} ${chalk38.yellow(`Validation failed for ${type2}${isSpecial ? "" : ` "${effectiveSlug}"`}:`)}`
|
|
35036
35648
|
);
|
|
35037
35649
|
for (const issue of parsed.error.issues) {
|
|
35038
35650
|
const field2 = issue.path.join(".") || "root";
|
|
@@ -35050,15 +35662,24 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
35050
35662
|
return;
|
|
35051
35663
|
}
|
|
35052
35664
|
if (opts.json) {
|
|
35053
|
-
console.log(
|
|
35665
|
+
console.log(
|
|
35666
|
+
JSON.stringify({
|
|
35667
|
+
valid: true,
|
|
35668
|
+
type: type2,
|
|
35669
|
+
slug: effectiveSlug,
|
|
35670
|
+
path: filePath
|
|
35671
|
+
})
|
|
35672
|
+
);
|
|
35054
35673
|
return;
|
|
35055
35674
|
}
|
|
35056
35675
|
console.log();
|
|
35057
|
-
console.log(
|
|
35676
|
+
console.log(
|
|
35677
|
+
` ${symbols.success} Validated ${type2}${isSpecial ? "" : ` ${chalk38.cyan(effectiveSlug)}`}`
|
|
35678
|
+
);
|
|
35058
35679
|
console.log();
|
|
35059
35680
|
} catch (err) {
|
|
35060
35681
|
commandError(
|
|
35061
|
-
`Failed to validate ${type2} "${
|
|
35682
|
+
`Failed to validate ${type2}${isSpecial ? "" : ` "${effectiveSlug}"`} after edit: ${err instanceof Error ? err.message : String(err)}`,
|
|
35062
35683
|
{
|
|
35063
35684
|
...opts,
|
|
35064
35685
|
code: "validation_error",
|
|
@@ -35067,6 +35688,7 @@ async function editEntity(type2, slug, opts = {}) {
|
|
|
35067
35688
|
);
|
|
35068
35689
|
}
|
|
35069
35690
|
}
|
|
35691
|
+
var EDITABLE_TYPES;
|
|
35070
35692
|
var init_edit_entity = __esm({
|
|
35071
35693
|
"src/commands/edit-entity.ts"() {
|
|
35072
35694
|
"use strict";
|
|
@@ -35075,6 +35697,7 @@ var init_edit_entity = __esm({
|
|
|
35075
35697
|
init_symbols();
|
|
35076
35698
|
init_context4();
|
|
35077
35699
|
init_validate();
|
|
35700
|
+
EDITABLE_TYPES = [...ENTITY_TYPES, "testability", "context"];
|
|
35078
35701
|
}
|
|
35079
35702
|
});
|
|
35080
35703
|
|
|
@@ -35125,7 +35748,7 @@ async function settle(page, settleMs) {
|
|
|
35125
35748
|
});
|
|
35126
35749
|
await page.waitForLoadState("networkidle", { timeout: settleMs }).catch(() => {
|
|
35127
35750
|
});
|
|
35128
|
-
const spaSettle = Math.min(Math.max(settleMs,
|
|
35751
|
+
const spaSettle = Math.min(Math.max(settleMs, 25), 60);
|
|
35129
35752
|
await page.waitForTimeout(spaSettle).catch(() => {
|
|
35130
35753
|
});
|
|
35131
35754
|
}
|
|
@@ -35193,7 +35816,7 @@ async function crawl(page, opts) {
|
|
|
35193
35816
|
}
|
|
35194
35817
|
async function crawlFrom(page, opts, origin, takeBlocked) {
|
|
35195
35818
|
const deny = opts.deny ?? [];
|
|
35196
|
-
const settleMs = opts.settleMs ??
|
|
35819
|
+
const settleMs = opts.settleMs ?? 400;
|
|
35197
35820
|
const { budget } = opts;
|
|
35198
35821
|
const stats = {
|
|
35199
35822
|
surfaces: 0,
|
|
@@ -36354,12 +36977,13 @@ async function startRun(scenarioSlugArg, opts) {
|
|
|
36354
36977
|
return;
|
|
36355
36978
|
}
|
|
36356
36979
|
const fallbackTarget = await testabilityTarget2(projectRoot);
|
|
36980
|
+
const targetSource = opts.target ? "--target" : fm.target_url ? `scenario (${scenarioSlug})` : fallbackTarget ? "iamu/testability.md" : null;
|
|
36357
36981
|
const start2 = resolveStartUrl(opts.target, fm, fallbackTarget);
|
|
36358
36982
|
if ("error" in start2) {
|
|
36359
36983
|
commandError(start2.error, {
|
|
36360
36984
|
...opts,
|
|
36361
36985
|
code: "invalid_target_url",
|
|
36362
|
-
fix:
|
|
36986
|
+
fix: "iamu set testability --target <url>"
|
|
36363
36987
|
});
|
|
36364
36988
|
return;
|
|
36365
36989
|
}
|
|
@@ -36370,12 +36994,13 @@ async function startRun(scenarioSlugArg, opts) {
|
|
|
36370
36994
|
const probe = await checkTargetReachable(start2.url);
|
|
36371
36995
|
targetReachable = probe.reachable;
|
|
36372
36996
|
if (!probe.reachable) {
|
|
36373
|
-
|
|
36997
|
+
const fixCmd = fm.target_url ? `iamu set testability --target <url> && iamu set scenario ${scenarioSlug} --no-target` : "iamu set testability --target <url>";
|
|
36998
|
+
targetWarning = `Target URL "${start2.url}" is unreachable (${probe.error ?? "connection refused"}). Ensure your dev server is running, or update the URL with: ${fixCmd}`;
|
|
36374
36999
|
if (opts.check === true) {
|
|
36375
37000
|
commandError(targetWarning, {
|
|
36376
37001
|
...opts,
|
|
36377
37002
|
code: "target_unreachable",
|
|
36378
|
-
fix:
|
|
37003
|
+
fix: fixCmd
|
|
36379
37004
|
});
|
|
36380
37005
|
return;
|
|
36381
37006
|
}
|
|
@@ -36465,6 +37090,7 @@ async function startRun(scenarioSlugArg, opts) {
|
|
|
36465
37090
|
path: path64.relative(projectRoot, run.absPath),
|
|
36466
37091
|
scenario_slug: scenarioSlug,
|
|
36467
37092
|
target_url: start2.url,
|
|
37093
|
+
target_source: targetSource,
|
|
36468
37094
|
target_reachable: targetReachable,
|
|
36469
37095
|
...targetWarning ? { target_warning: targetWarning } : {},
|
|
36470
37096
|
...liveRes?.viewerUrl ? { live_viewer_url: liveRes.viewerUrl } : {},
|
|
@@ -36493,7 +37119,9 @@ async function startRun(scenarioSlugArg, opts) {
|
|
|
36493
37119
|
console.log(` ${chalk40.dim("scenario:")} ${scenarioSlug}`);
|
|
36494
37120
|
console.log(` ${chalk40.dim("persona:")} ${fm.persona}`);
|
|
36495
37121
|
console.log(` ${chalk40.dim("outline:")} ${fm.outline}`);
|
|
36496
|
-
console.log(
|
|
37122
|
+
console.log(
|
|
37123
|
+
` ${chalk40.dim("target:")} ${start2.url}${targetSource ? chalk40.dim(` (from ${targetSource})`) : ""}`
|
|
37124
|
+
);
|
|
36497
37125
|
console.log(
|
|
36498
37126
|
` ${chalk40.dim("models:")} walker: ${chalk40.cyan(walker)} \xB7 judge: ${chalk40.cyan(judge)} \xB7 matcher: ${chalk40.cyan(matcher)}`
|
|
36499
37127
|
);
|
|
@@ -36569,14 +37197,18 @@ __export(instructions_walk_exports, {
|
|
|
36569
37197
|
extractUntestableBoundaries: () => extractUntestableBoundaries,
|
|
36570
37198
|
instructionsWalk: () => instructionsWalk,
|
|
36571
37199
|
revisitSection: () => revisitSection,
|
|
37200
|
+
selfCommand: () => selfCommand,
|
|
36572
37201
|
viewportLine: () => viewportLine
|
|
36573
37202
|
});
|
|
36574
37203
|
import { existsSync as existsSync20, readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "fs";
|
|
36575
37204
|
import { join as join10 } from "path";
|
|
36576
37205
|
import chalk41 from "chalk";
|
|
36577
|
-
function selfCommand() {
|
|
36578
|
-
const q = (p) =>
|
|
36579
|
-
|
|
37206
|
+
function selfCommand(execPath = process.execPath, scriptPath = process.argv[1] ?? "iamu") {
|
|
37207
|
+
const q = (p) => {
|
|
37208
|
+
const norm = p.replace(/\\/g, "/");
|
|
37209
|
+
return /[\s"'$]/.test(norm) ? JSON.stringify(norm) : norm;
|
|
37210
|
+
};
|
|
37211
|
+
return `${q(execPath)} ${q(scriptPath)}`;
|
|
36580
37212
|
}
|
|
36581
37213
|
function viewportLine(v) {
|
|
36582
37214
|
return `The browser is sized as ${SCREEN[v.device]}, ${v.width}\xD7${v.height}.`;
|
|
@@ -36585,18 +37217,20 @@ function browseSurface(iamu, viewport) {
|
|
|
36585
37217
|
return [
|
|
36586
37218
|
"`iamu browse` \u2014 a visible Chrome the orchestrator has already started.",
|
|
36587
37219
|
"",
|
|
36588
|
-
"Drive and record each step atomically in one command:",
|
|
37220
|
+
"Drive and record each step atomically in one command (automatically saves the screenshot AND prints the updated `[#N]` interactive elements + accessibility snapshot):",
|
|
36589
37221
|
` ${iamu} browse step --run <run-id> --action "<action>" --note "<monologue>" <command> [args...]`,
|
|
36590
37222
|
"",
|
|
37223
|
+
"Chain consecutive form fills or actions on the same screen into a single step with `--then` (repeatable):",
|
|
37224
|
+
` ${iamu} browse step --run <run-id> --action "<action>" --note "<monologue>" fill-index 21 "2026-11-09" --then 'fill-index 22 "2026-11-20"'`,
|
|
37225
|
+
"",
|
|
36591
37226
|
"Supported commands (run with step or standalone):",
|
|
36592
|
-
` ${iamu} browse goto <url
|
|
36593
|
-
` ${iamu} browse snapshot
|
|
36594
|
-
` ${iamu} browse
|
|
37227
|
+
` ${iamu} browse goto <url> # Opens URL and prints [#N] elements + snapshot`,
|
|
37228
|
+
` ${iamu} browse snapshot --indexed # Re-inspect current page (not needed after goto or step)`,
|
|
37229
|
+
` ${iamu} browse click-index <number> # Click element by [#N] index (preferred, fast)`,
|
|
37230
|
+
` ${iamu} browse fill-index <number> "<text>" [--submit]`,
|
|
36595
37231
|
` ${iamu} browse click <role> "<name>" [--nth N]`,
|
|
36596
37232
|
` ${iamu} browse click-text "<visible text>"`,
|
|
36597
|
-
` ${iamu} browse click-index <number> # Click element by [#N] index`,
|
|
36598
37233
|
` ${iamu} browse fill <role> "<name>" "<text>" [--submit]`,
|
|
36599
|
-
` ${iamu} browse fill-index <number> "<text>" [--submit]`,
|
|
36600
37234
|
` ${iamu} browse select <role> "<name>" "<option>"`,
|
|
36601
37235
|
` ${iamu} browse upload <role> "<name>" "<file-path>"`,
|
|
36602
37236
|
` ${iamu} browse upload-index <number> "<file-path>"`,
|
|
@@ -36609,7 +37243,11 @@ function browseSurface(iamu, viewport) {
|
|
|
36609
37243
|
` ${iamu} browse shot <png> [--full]`,
|
|
36610
37244
|
` ${iamu} browse wait [ms] # Wait for page/animations to settle`,
|
|
36611
37245
|
"",
|
|
36612
|
-
"
|
|
37246
|
+
"Fast-driving rules:",
|
|
37247
|
+
"\u2022 `browse goto` and `browse step` automatically print the fresh `[#N]` interactive elements and accessibility snapshot. Do NOT run `browse snapshot` after `browse goto` or `browse step`.",
|
|
37248
|
+
"\u2022 Prefer `click-index <N>` and `fill-index <N>` using the `[#N]` indices from the previous step's output.",
|
|
37249
|
+
"\u2022 Do NOT read step screenshot PNG files with your file-reading tool after routine steps \u2014 the snapshot output already shows all text, roles, and states. Only read a screenshot file if you suspect a visual rendering issue that contradicts the snapshot.",
|
|
37250
|
+
"\u2022 Do not run `browse start` or `browse stop`, and do not drive the product any other way. The browser gives you no sound: you may report that an audio control gave no visible confirmation, never what the audio said.",
|
|
36613
37251
|
...viewport ? [viewportLine(viewport)] : []
|
|
36614
37252
|
].join("\n");
|
|
36615
37253
|
}
|
|
@@ -36746,14 +37384,14 @@ function composeWalkerPrompt(p) {
|
|
|
36746
37384
|
"The CLI handles step numbering, timestamps, screenshot capture/linking, and telemetry automatically.",
|
|
36747
37385
|
`Screenshots to: ${rd}/screenshots/step-N.png \u2014 absolute paths.`,
|
|
36748
37386
|
`Record findings with: ${iamu} new-finding --scenario ${p.scenarioSlug} --run ${p.runId} --step <N> --title "..." --type <helps|hinders|missing|irrelevant|incorrect> --severity <critical|major|minor|nit> --ui-fact "..." --consequence "..." --task-impact <blocks|impairs|slows|neutral|accelerates|enables> --confidence <high|medium|low> --need "<what you needed, no solution>" --description "..." --json`,
|
|
36749
|
-
"`--step <N>` is the step whose screenshot shows the finding (or strength): it is the evidence people see next to the finding, so record that step and its screenshot first, and pick the screenshot where the thing is actually visible.",
|
|
37387
|
+
"`--step <N>` is the step whose screenshot shows the finding (or strength): it is the evidence people see next to the finding, so record that step and its screenshot first, and pick the screenshot where the thing is actually visible. When recording multiple findings at the end of a screen or walk, batch them in a single shell command chained with `&&` (and you may chain `iamu summary` after them) rather than running one finding per turn.",
|
|
36750
37388
|
`Before you finish, record synthesis with: ${iamu} summary ${p.runId} --reached "..." | --stopped "..." --worked "..." --worked "..." --question "..." [--setup-note "..."]. --reached: you got to what you came to do (the last thing you did and what the product showed); --stopped: you ended before it, and why. One of the two, always. --worked: what worked for this persona, one point each. --question: decisions for the product team the walk raised. --setup-note: doubts about the test setup (missing data, accounts, dates), never the product.`,
|
|
36751
37389
|
`If the walk cannot start as it was set up \u2014 you cannot become this persona (the switch or login fails, or you are someone else), the world the situation needs is not there and you cannot create it, or the starting page does not load \u2014 stop immediately and end the walk with: ${iamu} summary ${p.runId} --blocked "<what was missing, as you saw it>". Do NOT record preliminary findings if preconditions are not met. That is about the setup, not the product: when the product has no way to do what you need, that is a finding (--type missing), not a block. Do not record findings about the missing setup itself.`,
|
|
36752
37390
|
"Execute everything via CLI commands in the shell. Never invoke host Edit or Write tools on files in the workspace.",
|
|
36753
37391
|
"",
|
|
36754
37392
|
"Do not read the product's source code, other iamu/ entity files, or earlier runs: you know only what the persona knows.",
|
|
36755
37393
|
"",
|
|
36756
|
-
"When done,
|
|
37394
|
+
"When done, reply with a concise one-line status (`Walk complete: <N> steps, <M> findings`) \u2014 do not re-type the full findings or synthesis into chat, as the orchestrator reads them directly from disk."
|
|
36757
37395
|
].join("\n");
|
|
36758
37396
|
}
|
|
36759
37397
|
async function instructionsWalk(opts) {
|
|
@@ -39726,13 +40364,19 @@ function registerFindingsCommands(program) {
|
|
|
39726
40364
|
|
|
39727
40365
|
// src/commands/register-project.ts
|
|
39728
40366
|
function registerProjectCommands(program) {
|
|
39729
|
-
program.command("init").helpGroup("Project:").description("Scaffold iamu/ + install skills").argument("[name]", "Project name (defaults to directory name)").option("-y, --yes", "Skip prompts and accept all detected defaults").option("--cloud", "Connect to Iamu Cloud directly during init").option("--no-cloud", "Skip Iamu Cloud connection (local-only)").option("--json", "Output initialization metadata as JSON").addHelpText(
|
|
40367
|
+
program.command("init").helpGroup("Project:").description("Scaffold iamu/ + install skills").argument("[name]", "Project name (defaults to directory name)").option("-y, --yes", "Skip prompts and accept all detected defaults").option("--target <url>", "Target URL for local or staging walkthroughs").option("--cloud", "Connect to Iamu Cloud directly during init").option("--no-cloud", "Skip Iamu Cloud connection (local-only)").option("--json", "Output initialization metadata as JSON").addHelpText(
|
|
39730
40368
|
"after",
|
|
39731
|
-
"\nExamples:\n $ iamu init\n $ iamu init my-project -y\n $ iamu init --cloud\n $ iamu init --json"
|
|
40369
|
+
"\nExamples:\n $ iamu init\n $ iamu init my-project -y\n $ iamu init --target http://localhost:5173\n $ iamu init --cloud\n $ iamu init --json"
|
|
39732
40370
|
).action(
|
|
39733
40371
|
async (name, opts) => {
|
|
39734
40372
|
const { init: init2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
39735
|
-
await init2({
|
|
40373
|
+
await init2({
|
|
40374
|
+
name,
|
|
40375
|
+
yes: opts.yes,
|
|
40376
|
+
json: opts.json,
|
|
40377
|
+
cloud: opts.cloud,
|
|
40378
|
+
target: opts.target
|
|
40379
|
+
});
|
|
39736
40380
|
}
|
|
39737
40381
|
);
|
|
39738
40382
|
program.command("status").helpGroup("Project:").description("Show project state: entities, runs, context, cloud link").option("--json", "Output as JSON").addHelpText(
|
|
@@ -39754,7 +40398,7 @@ function registerProjectCommands(program) {
|
|
|
39754
40398
|
const { doctor: doctor2 } = await Promise.resolve().then(() => (init_doctor(), doctor_exports));
|
|
39755
40399
|
await doctor2(opts);
|
|
39756
40400
|
});
|
|
39757
|
-
program.command("preflight").helpGroup("Project:").description("Run fast typecheck
|
|
40401
|
+
program.command("preflight").helpGroup("Project:").description("Run fast typecheck check before walkthroughs").option("--cwd <path>", "Working directory of target project").option("--json", "Output diagnostics as JSON").addHelpText(
|
|
39758
40402
|
"after",
|
|
39759
40403
|
"\nExamples:\n $ iamu preflight\n $ iamu preflight --json"
|
|
39760
40404
|
).action(async (opts) => {
|
|
@@ -39894,8 +40538,8 @@ function registerProjectCommands(program) {
|
|
|
39894
40538
|
await setEntity2(type2, slug, opts);
|
|
39895
40539
|
}
|
|
39896
40540
|
);
|
|
39897
|
-
program.command("edit <type>
|
|
39898
|
-
"Open an entity file in your editor ($VISUAL or $EDITOR) and validate on exit"
|
|
40541
|
+
program.command("edit <type> [slug]").helpGroup("Project:").description(
|
|
40542
|
+
"Open an entity or project file (testability, context) in your editor ($VISUAL or $EDITOR) and validate on exit"
|
|
39899
40543
|
).option("--json", "Output as JSON").action(
|
|
39900
40544
|
async (type2, slug, opts = {}) => {
|
|
39901
40545
|
const { editEntity: editEntity2 } = await Promise.resolve().then(() => (init_edit_entity(), edit_entity_exports));
|
|
@@ -40084,6 +40728,13 @@ function registerWalkthroughCommands(program) {
|
|
|
40084
40728
|
).option("--action <text>", "Action description (step only)").option("--note <text>", "Persona inner monologue / reasoning (step only)").option("--surface <name>", "Screen or surface name (step only)").option(
|
|
40085
40729
|
"--step-file <path>",
|
|
40086
40730
|
"Read step payload (action, note, cmd) from a JSON file or '-' for stdin (step only)"
|
|
40731
|
+
).option(
|
|
40732
|
+
"--then <cmd>",
|
|
40733
|
+
"Chain an additional browser action in the same step before taking the screenshot (repeatable, step only)",
|
|
40734
|
+
(value, previous = []) => [...previous, value]
|
|
40735
|
+
).option(
|
|
40736
|
+
"--no-snapshot",
|
|
40737
|
+
"Omit the post-action accessibility snapshot from browse step / goto output"
|
|
40087
40738
|
).option("--width <px>", "Window width (start)").option("--height <px>", "Window height (start)").option("--headless", "Run without a window (start)").option(
|
|
40088
40739
|
"--port <port>",
|
|
40089
40740
|
"CDP port (start), default 9333",
|