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