waygraph 0.7.16 → 0.10.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.
Files changed (83) hide show
  1. package/README.md +90 -52
  2. package/dist/auto-explore-run.js +13 -7
  3. package/dist/auto-explore.js +47 -36
  4. package/dist/cli.d.ts +8 -12
  5. package/dist/cli.js +229 -249
  6. package/dist/engine.d.ts +53 -11
  7. package/dist/engine.js +81 -8
  8. package/dist/graph.d.ts +2 -1
  9. package/dist/graph.js +51 -11
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.js +1 -1
  12. package/examples/saucedemo/README.md +19 -5
  13. package/examples/saucedemo/docs/CONVENTION.md +22 -18
  14. package/examples/saucedemo/docs/HELPERS.md +152 -8
  15. package/examples/saucedemo/package-lock.json +1 -1
  16. package/examples/saucedemo/package.json +11 -5
  17. package/examples/saucedemo/scripts/capture-wrong-login.mjs +1 -1
  18. package/examples/saucedemo/scripts/drive-auto-cli.mjs +93 -0
  19. package/examples/saucedemo/scripts/prove-wrong-login-runtime.mjs +1 -1
  20. package/examples/saucedemo/scripts/show-auto-menu.mjs +72 -0
  21. package/examples/saucedemo/src/blocks/SITE-MAP.md +11 -8
  22. package/examples/saucedemo/src/blocks/saucedemo-web/NAV.md +3 -3
  23. package/examples/saucedemo/src/blocks/saucedemo-web/cart/NAV.md +1 -1
  24. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/NAV.md +1 -1
  25. package/{templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/actions/submit-checkout-info.action.block.ts → examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.ts} +5 -5
  26. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/NAV.md +1 -1
  27. package/examples/saucedemo/src/blocks/saucedemo-web/checkout-step-two/{actions/finish-order.action.block.ts → methods/finish-order.method.block.ts} +5 -5
  28. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/NAV.md +17 -5
  29. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/inventory.page.block.ts +37 -0
  30. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/add-all-to-cart.method.block.ts +55 -0
  31. package/{templates/quickstart/src/blocks/saucedemo-web/inventory/actions → examples/saucedemo/src/blocks/saucedemo-web/inventory/methods}/add-to-cart.effect.block.ts +6 -6
  32. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/{actions → methods}/inventory-items.ts +40 -20
  33. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/methods/remove-all-from-cart.method.block.ts +55 -0
  34. package/{templates/quickstart/src/blocks/saucedemo-web/inventory/actions → examples/saucedemo/src/blocks/saucedemo-web/inventory/methods}/remove-from-cart.effect.block.ts +5 -5
  35. package/examples/saucedemo/src/blocks/saucedemo-web/inventory/nav-item-detail.block.ts +4 -4
  36. package/examples/saucedemo/src/blocks/saucedemo-web/{actions → methods}/submit-login-for-flow.ts +2 -2
  37. package/examples/saucedemo/src/blocks/saucedemo-web/{actions/submit-login.action.block.ts → methods/submit-login.method.block.ts} +5 -5
  38. package/{templates/quickstart/src/blocks/saucedemo-web/actions/submit-logout.action.block.ts → examples/saucedemo/src/blocks/saucedemo-web/methods/submit-logout.method.block.ts} +5 -5
  39. package/examples/saucedemo/src/flows/cart-bulk.flow.ts +19 -0
  40. package/examples/saucedemo/src/flows/cart-mem.flow.ts +1 -1
  41. package/examples/saucedemo/src/flows/catalog-logout.flow.ts +1 -1
  42. package/examples/saucedemo/src/flows/checkout-then-browse-again.flow.ts +4 -4
  43. package/examples/saucedemo/src/flows/checkout.flow.ts +4 -4
  44. package/examples/saucedemo/src/flows/login.flow.ts +1 -1
  45. package/examples/saucedemo/src/flows/shop.flow.ts +3 -3
  46. package/examples/saucedemo/src/flows/smoke.flow.ts +2 -2
  47. package/examples/saucedemo/src/flows/viewer-blocked.flow.ts +1 -1
  48. package/examples/saucedemo/tests/flow-block-verify.spec.ts +1 -1
  49. package/examples/saucedemo/tests/submit-login-branch.spec.ts +1 -1
  50. package/package.json +1 -1
  51. package/templates/quickstart/README-saucedemo-commands.md +72 -0
  52. package/templates/quickstart/docs/CONVENTION.md +5 -5
  53. package/templates/quickstart/docs/HELPERS.md +152 -8
  54. package/templates/quickstart/package.json +9 -5
  55. package/templates/quickstart/scripts/drive-auto-cli.mjs +93 -0
  56. package/templates/quickstart/src/blocks/SITE-MAP.md +11 -8
  57. package/templates/quickstart/src/blocks/saucedemo-web/NAV.md +3 -3
  58. package/templates/quickstart/src/blocks/saucedemo-web/cart/NAV.md +1 -1
  59. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/NAV.md +1 -1
  60. package/{examples/saucedemo/src/blocks/saucedemo-web/checkout-step-one/actions/submit-checkout-info.action.block.ts → templates/quickstart/src/blocks/saucedemo-web/checkout-step-one/methods/submit-checkout-info.method.block.ts} +5 -5
  61. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/NAV.md +1 -1
  62. package/templates/quickstart/src/blocks/saucedemo-web/checkout-step-two/{actions/finish-order.action.block.ts → methods/finish-order.method.block.ts} +5 -5
  63. package/templates/quickstart/src/blocks/saucedemo-web/inventory/NAV.md +17 -5
  64. package/templates/quickstart/src/blocks/saucedemo-web/inventory/inventory.page.block.ts +37 -0
  65. package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/add-all-to-cart.method.block.ts +55 -0
  66. package/{examples/saucedemo/src/blocks/saucedemo-web/inventory/actions → templates/quickstart/src/blocks/saucedemo-web/inventory/methods}/add-to-cart.effect.block.ts +6 -6
  67. package/templates/quickstart/src/blocks/saucedemo-web/inventory/{actions → methods}/inventory-items.ts +40 -20
  68. package/templates/quickstart/src/blocks/saucedemo-web/inventory/methods/remove-all-from-cart.method.block.ts +55 -0
  69. package/{examples/saucedemo/src/blocks/saucedemo-web/inventory/actions → templates/quickstart/src/blocks/saucedemo-web/inventory/methods}/remove-from-cart.effect.block.ts +5 -5
  70. package/templates/quickstart/src/blocks/saucedemo-web/inventory/nav-item-detail.block.ts +1 -1
  71. package/templates/quickstart/src/blocks/saucedemo-web/{actions → methods}/submit-login-for-flow.ts +2 -2
  72. package/templates/quickstart/src/blocks/saucedemo-web/{actions/submit-login.action.block.ts → methods/submit-login.method.block.ts} +5 -5
  73. package/{examples/saucedemo/src/blocks/saucedemo-web/actions/submit-logout.action.block.ts → templates/quickstart/src/blocks/saucedemo-web/methods/submit-logout.method.block.ts} +5 -5
  74. package/templates/quickstart/src/flows/cart-bulk.flow.ts +19 -0
  75. package/templates/quickstart/src/flows/cart-mem.flow.ts +1 -1
  76. package/templates/quickstart/src/flows/catalog-logout.flow.ts +1 -1
  77. package/templates/quickstart/src/flows/checkout-then-browse-again.flow.ts +4 -4
  78. package/templates/quickstart/src/flows/checkout.flow.ts +4 -4
  79. package/templates/quickstart/src/flows/login.flow.ts +1 -1
  80. package/templates/quickstart/src/flows/shop.flow.ts +3 -3
  81. package/templates/quickstart/src/flows/smoke.flow.ts +2 -2
  82. package/templates/quickstart/src/flows/viewer-blocked.flow.ts +1 -1
  83. package/templates/scaffold/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2,26 +2,21 @@
2
2
  /**
3
3
  * waygraph CLI -- tooling around this same package's engine.
4
4
  *
5
- * Commands (watch first, then run, then inspect, then scaffold):
6
- * demo <flow|spec> [project] Watch a flow/chain with step overlay (default STEP+headed)
7
- * try [demo|auto] One-shot saucedemo in an OS temp dir (demo=stepper, auto=explore)
8
- * chain <spec> [project] Ad-hoc Blocks by name (+ --step/--autoplay like demo)
9
- * run <flow> [project] Run a named flow (no overlay unless --step)
10
- * list / nav / validate Discover and inspect flows
11
- * check / auto / graph Hygiene + state graph
12
- * init <name> Offline scaffold
5
+ * Primary verbs (less is more):
6
+ * auto Explore picker; `--blocks From To` = graph path-find + run
7
+ * demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--auto-play-video`
8
+ * run Execute; `--blocks` `--data` `--non-headless` `--video`
13
9
  *
14
- * "project" defaults to the current directory.
15
- * A project is any directory containing *.flow.ts files (typically under src/flows/).
16
- * Run flags (--step/--autoplay/--base-url/--title/--video) beat WAYGRAPH_* env when present.
17
- * demo defaults: --step on, --autoplay off (manual "Run this step" / "Next").
10
+ * Also: list / nav / validate / check / graph / init / try
11
+ * Aliases (one release): `chain` -> run/demo --blocks; `--autoplay` -> `--auto-next`
12
+ *
13
+ * "project" defaults to cwd. Flags beat WAYGRAPH_* env.
18
14
  */
19
15
  import { existsSync, readdirSync, readFileSync, writeFileSync, rmSync, cpSync, mkdirSync, mkdtempSync } from "node:fs";
20
16
  import { resolve, relative, join, basename, dirname } from "node:path";
21
17
  import { tmpdir } from "node:os";
22
18
  import { fileURLToPath } from "node:url";
23
19
  import { spawn } from "node:child_process";
24
- import { MemPage } from "./mem-page.js";
25
20
  import { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./graph.js";
26
21
  import { runAutoExplore } from "./auto-explore-run.js";
27
22
  // ---------------------------------------------------------------------------
@@ -313,10 +308,16 @@ function seedMemFromRequires(mem, requires, json, label) {
313
308
  return;
314
309
  }
315
310
  if (json === undefined) {
316
- throw new Error(
317
- "waygraph chain: \\"" + label + "\\" requires " + requires.map((k) => k.name).join(", ") +
318
- " - give a JSON payload, e.g. " + label + "({...})",
319
- );
311
+ // --data / WAYGRAPH_DATA supplies the same payload shape as blockName({...}).
312
+ const fromEnv = process.env.WAYGRAPH_DATA;
313
+ if (fromEnv && fromEnv.trim()) {
314
+ json = fromEnv;
315
+ } else {
316
+ throw new Error(
317
+ "waygraph chain: \\"" + label + "\\" requires " + requires.map((k) => k.name).join(", ") +
318
+ " - give a JSON payload (inline blockName({...}) or --data '{...}')",
319
+ );
320
+ }
320
321
  }
321
322
  let parsed;
322
323
  try {
@@ -324,6 +325,15 @@ function seedMemFromRequires(mem, requires, json, label) {
324
325
  } catch (err) {
325
326
  throw new Error("waygraph chain: \\"" + label + "\\" payload is not valid JSON - " + String(err));
326
327
  }
328
+ // Keyed-by-name when every require name is a top-level key (preferred for
329
+ // --data and for {"saucedemo.credentials":{...}} even with one require).
330
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
331
+ const keyed = requires.every((k) => k.name in parsed);
332
+ if (keyed) {
333
+ for (const k of requires) mem.set(k, parsed[k.name]);
334
+ return;
335
+ }
336
+ }
327
337
  if (requires.length === 1) {
328
338
  mem.set(requires[0], parsed);
329
339
  return;
@@ -2539,8 +2549,8 @@ async function runTryDemo() {
2539
2549
  "Keep a permanent scaffold in your tree: waygraph init my-app\n");
2540
2550
  }
2541
2551
  /**
2542
- * `waygraph try auto` - same temp Sauce Demo as try demo, but opens the
2543
- * headed `waygraph auto` explorer (Effect Add/Remove, MemNav Open details).
2552
+ * `waygraph try auto` - same temp Sauce Demo as try demo, then headed
2553
+ * `waygraph auto` (same menus as `npm run auto:cli`).
2544
2554
  */
2545
2555
  async function runTryAuto() {
2546
2556
  const destDir = await prepareTryQuickstart("waygraph-try-auto");
@@ -2548,16 +2558,18 @@ async function runTryAuto() {
2548
2558
  return;
2549
2559
  process.env.WAYGRAPH_BASE_URL ??= "https://www.saucedemo.com";
2550
2560
  console.log("waygraph try auto: headed explore on Sauce Demo (temp dir).\n" +
2551
- " Pick nav-login, then submit-login (creds are pre-seeded).\n" +
2561
+ " Prefer CLI next time: cd <temp> && npm run auto:cli\n" +
2562
+ " Pick LoginPage / submit-login (creds pre-seeded via --data on auto:cli).\n" +
2552
2563
  " On inventory: Add/Remove Effect rows + Open details MemNav.\n" +
2553
2564
  " Quit from the panel when done.\n");
2554
2565
  await runAutoExplore(destDir, { baseURL: process.env.WAYGRAPH_BASE_URL });
2555
2566
  console.log("\nwaygraph try auto: explorer closed.\n\n" +
2556
2567
  `Temp project:\n ${destDir}\n\n` +
2557
- "Run again:\n" +
2568
+ "Run again (CLI first):\n" +
2569
+ ` cd ${destDir} && npm run auto:cli\n` +
2558
2570
  ` cd ${destDir} && npm run auto\n\n` +
2559
2571
  "In-package: examples/saucedemo\n" +
2560
- "Docs: https://deviate-dv8.github.io/waygraph/saucedemo/\n");
2572
+ "Docs: https://deviate-dv8.github.io/waygraph/auto.html\n");
2561
2573
  }
2562
2574
  async function validateFlows(projectDir) {
2563
2575
  const files = discoverFlows(projectDir);
@@ -2715,36 +2727,34 @@ async function checkCommand(projectDir) {
2715
2727
  }
2716
2728
  return warnings;
2717
2729
  }
2718
- // ---------------------------------------------------------------------------
2719
- // `run`
2720
- // ---------------------------------------------------------------------------
2721
- async function runFlow(projectDir, flowName) {
2722
- const files = discoverFlows(projectDir);
2723
- for (const file of files) {
2724
- let mod;
2725
- try {
2726
- mod = await importFlowFile(file);
2727
- }
2728
- catch {
2729
- continue;
2730
- }
2731
- for (const [exportName, exported] of Object.entries(mod)) {
2732
- if (!isFlowLike(exported) || exportName !== flowName)
2733
- continue;
2734
- console.log(`waygraph: running flow "${flowName}" from ${relative(projectDir, file)}`);
2735
- const result = await exported.run(new MemPage());
2736
- console.log(`waygraph: flow finished -- ${JSON.stringify(result)}`);
2737
- return;
2738
- }
2730
+ function takeFlagValue(argv, i, a, flag) {
2731
+ if (a.startsWith(flag + "=")) {
2732
+ return { value: a.slice(flag.length + 1), nextI: i };
2739
2733
  }
2740
- console.error(`waygraph: no flow named "${flowName}" found under ${projectDir}`);
2741
- process.exitCode = 1;
2734
+ const v = argv[i + 1];
2735
+ if (v === undefined || (v.startsWith("-") && !a.includes("="))) {
2736
+ console.error(`waygraph: ${flag} needs a value`);
2737
+ process.exit(1);
2738
+ }
2739
+ return { value: v, nextI: i + 1 };
2740
+ }
2741
+ /**
2742
+ * Looks like a Checkpoint tag for auto --blocks From To (PascalCase).
2743
+ * Kebab-case Block names must not trigger path-find mode.
2744
+ */
2745
+ function looksLikeCheckpointArg(s) {
2746
+ if (!s || s.startsWith("-"))
2747
+ return false;
2748
+ if (s.includes("then") || s.includes("(") || s.includes("{") || s.includes("/"))
2749
+ return false;
2750
+ if (s === "." || s.includes("."))
2751
+ return false;
2752
+ return /^[A-Z][A-Za-z0-9_]*$/.test(s);
2742
2753
  }
2743
2754
  /**
2744
- * Pulls `--step` / `--no-step` / `--autoplay` / `--no-autoplay` /
2745
- * `--base-url <url>` / `--title <text>` out of argv. Unknown `--*` stay as
2746
- * positionals so callers like `auto --mermaid` keep working when they parse
2747
- * their own flags.
2755
+ * Pulls run/demo/auto flags out of argv. Unknown bare `--*` that are not
2756
+ * recognized stay as positionals only when they are project paths; known
2757
+ * explore flags (--cli/--mermaid/--map) are parsed here.
2748
2758
  */
2749
2759
  function parseRunFlags(argv) {
2750
2760
  const out = { positionals: [] };
@@ -2756,35 +2766,75 @@ function parseRunFlags(argv) {
2756
2766
  else if (a === "--no-step") {
2757
2767
  out.step = false;
2758
2768
  }
2759
- else if (a === "--autoplay") {
2769
+ else if (a === "--auto-next" || a === "--autoplay") {
2760
2770
  out.autoplay = true;
2761
2771
  }
2762
- else if (a === "--no-autoplay") {
2772
+ else if (a === "--no-auto-next" || a === "--no-autoplay") {
2763
2773
  out.autoplay = false;
2764
2774
  }
2775
+ else if (a === "--auto-play-video") {
2776
+ out.autoPlayVideo = true;
2777
+ }
2778
+ else if (a === "--non-headless") {
2779
+ out.nonHeadless = true;
2780
+ }
2781
+ else if (a === "--cli") {
2782
+ out.cli = true;
2783
+ }
2784
+ else if (a === "--mermaid") {
2785
+ out.mermaid = true;
2786
+ }
2787
+ else if (a === "--map") {
2788
+ out.map = true;
2789
+ }
2765
2790
  else if (a === "--base-url" || a.startsWith("--base-url=")) {
2766
- const v = a.includes("=") ? a.slice("--base-url=".length) : argv[++i];
2767
- if (!v || v.startsWith("-")) {
2768
- console.error("waygraph: --base-url needs a URL value");
2769
- process.exit(1);
2770
- }
2771
- out.baseUrl = v;
2791
+ const t = takeFlagValue(argv, i, a, "--base-url");
2792
+ out.baseUrl = t.value;
2793
+ i = t.nextI;
2772
2794
  }
2773
2795
  else if (a === "--title" || a.startsWith("--title=")) {
2774
- const v = a.includes("=") ? a.slice("--title=".length) : argv[++i];
2775
- if (v === undefined || (v.startsWith("-") && !a.includes("="))) {
2776
- console.error('waygraph: --title needs a string value');
2777
- process.exit(1);
2778
- }
2779
- out.title = v;
2796
+ const t = takeFlagValue(argv, i, a, "--title");
2797
+ out.title = t.value;
2798
+ i = t.nextI;
2799
+ }
2800
+ else if (a === "--data" || a.startsWith("--data=")) {
2801
+ const t = takeFlagValue(argv, i, a, "--data");
2802
+ out.data = t.value;
2803
+ i = t.nextI;
2780
2804
  }
2781
2805
  else if (a === "--video" || a.startsWith("--video=")) {
2782
- const v = a.includes("=") ? a.slice("--video=".length) : argv[++i];
2783
- if (v === undefined || (v.startsWith("-") && !a.includes("="))) {
2784
- out.video = "";
2806
+ if (a.startsWith("--video=")) {
2807
+ out.video = a.slice("--video=".length);
2785
2808
  }
2786
2809
  else {
2787
- out.video = v;
2810
+ const v = argv[i + 1];
2811
+ if (v === undefined || v.startsWith("-")) {
2812
+ out.video = "";
2813
+ }
2814
+ else {
2815
+ out.video = v;
2816
+ i++;
2817
+ }
2818
+ }
2819
+ }
2820
+ else if (a === "--blocks" || a.startsWith("--blocks=")) {
2821
+ if (a.startsWith("--blocks=")) {
2822
+ out.blocks = a.slice("--blocks=".length);
2823
+ }
2824
+ else {
2825
+ const v1 = argv[++i];
2826
+ if (!v1 || v1.startsWith("-")) {
2827
+ console.error("waygraph: --blocks needs a flow/spec or <from> <to> checkpoints");
2828
+ process.exit(1);
2829
+ }
2830
+ const v2 = argv[i + 1];
2831
+ if (v2 && looksLikeCheckpointArg(v1) && looksLikeCheckpointArg(v2)) {
2832
+ out.blocksFromTo = [v1, v2];
2833
+ i++;
2834
+ }
2835
+ else {
2836
+ out.blocks = v1;
2837
+ }
2788
2838
  }
2789
2839
  }
2790
2840
  else {
@@ -2794,7 +2844,19 @@ function parseRunFlags(argv) {
2794
2844
  return out;
2795
2845
  }
2796
2846
  /** Writes flag values into process.env so the chain child inherits them. Flags beat prior env. */
2797
- function applyRunFlags(flags) {
2847
+ function applyRunFlags(flags, opts) {
2848
+ if (flags.autoPlayVideo) {
2849
+ if (!opts?.allowAutoPlayVideo) {
2850
+ console.error("waygraph: --auto-play-video is a demo-only flag (QA watch + record)");
2851
+ process.exit(1);
2852
+ }
2853
+ if (flags.autoplay === undefined)
2854
+ flags.autoplay = true;
2855
+ if (flags.video === undefined)
2856
+ flags.video = "";
2857
+ if (flags.step === undefined)
2858
+ flags.step = true;
2859
+ }
2798
2860
  if (flags.step === true) {
2799
2861
  process.env.WAYGRAPH_STEP = "1";
2800
2862
  process.env.WAYGRAPH_HEADED = "1";
@@ -2808,6 +2870,9 @@ function applyRunFlags(flags) {
2808
2870
  else if (flags.autoplay === false) {
2809
2871
  process.env.WAYGRAPH_AUTOPLAY = "0";
2810
2872
  }
2873
+ if (flags.nonHeadless) {
2874
+ process.env.WAYGRAPH_HEADED = "1";
2875
+ }
2811
2876
  if (flags.baseUrl !== undefined) {
2812
2877
  process.env.WAYGRAPH_BASE_URL = flags.baseUrl;
2813
2878
  }
@@ -2817,6 +2882,9 @@ function applyRunFlags(flags) {
2817
2882
  if (flags.video !== undefined) {
2818
2883
  process.env.WAYGRAPH_VIDEO = flags.video || "1";
2819
2884
  }
2885
+ if (flags.data !== undefined) {
2886
+ process.env.WAYGRAPH_DATA = flags.data;
2887
+ }
2820
2888
  }
2821
2889
  /**
2822
2890
  * BASE_URL when neither --base-url nor WAYGRAPH_BASE_URL is set:
@@ -2859,8 +2927,7 @@ function applyDemoDefaults(projectDir) {
2859
2927
  if (process.env.WAYGRAPH_STEP === "1" && process.env.WAYGRAPH_HEADED === undefined) {
2860
2928
  process.env.WAYGRAPH_HEADED = "1";
2861
2929
  }
2862
- // Manual Next by default. --autoplay flips this; --no-step --video (unattended)
2863
- // still prefers autoplay when the caller did not set it.
2930
+ // Manual Next by default. --auto-next / --auto-play-video flip this.
2864
2931
  if (process.env.WAYGRAPH_AUTOPLAY === undefined) {
2865
2932
  if (process.env.WAYGRAPH_VIDEO && process.env.WAYGRAPH_STEP === "0") {
2866
2933
  process.env.WAYGRAPH_AUTOPLAY = "1";
@@ -2875,169 +2942,49 @@ function applyDemoDefaults(projectDir) {
2875
2942
  process.env.WAYGRAPH_BASE_URL = resolved;
2876
2943
  }
2877
2944
  }
2945
+ function resolveProjectDir(flags, fallbackIndex = 0) {
2946
+ const cand = flags.positionals[fallbackIndex] ?? process.cwd();
2947
+ return resolve(cand);
2948
+ }
2949
+ function resolveSpec(flags) {
2950
+ return flags.blocks ?? flags.positionals[0];
2951
+ }
2878
2952
  const args = process.argv.slice(2);
2879
2953
  const command = args[0];
2880
2954
  function usage() {
2881
2955
  console.log(`waygraph -- graph project tool + engine CLI
2882
2956
 
2883
- Usage (watch -> run -> inspect -> scaffold):
2884
- waygraph demo <flow|spec> [project]
2885
- Watch a flow or chain with the step overlay.
2886
- Defaults: --step ON, --autoplay OFF (manual
2887
- "Run this step" / "Next"). BASE_URL from
2888
- --base-url, else WAYGRAPH_BASE_URL, else
2889
- package.json waygraph.baseUrl, else
2890
- playwright.config baseURL.
2891
- waygraph try [demo|auto] One-shot saucedemo (temp dir): demo=stepper+test, auto=explore
2892
- waygraph chain <spec> [project] Ad-hoc Blocks by name ("a then b"); add
2893
- --step / --autoplay for the same overlay
2894
- waygraph run <flow> [project] Run a named flow (no overlay unless --step)
2895
- waygraph list [project] List discovered flows
2896
- waygraph nav [flow] [project] Print flow chain + navigation steps
2897
- waygraph validate [project] Import and validate all flows
2898
- waygraph check [project] Nav hygiene + orphan Blocks
2899
- waygraph auto [project] Interactive explore (picker; --cli terminal)
2900
- waygraph graph [project] Static state graph (JSON or --mermaid)
2901
- waygraph init <name> Offline scaffold (same as create-waygraph)
2902
-
2903
- Demo / step / autoplay (read this - agents get this wrong):
2904
- --step headed overlay: gate each Block, edit MemKeys,
2905
- highlight verify Traits, then Next
2906
- --no-step no overlay; run straight through
2907
- --autoplay start with "Auto-advance" CHECKED (timer)
2908
- --no-autoplay start with Auto-advance UNCHECKED (manual)
2909
- Mid-run: flip the panel checkbox anytime. A manual click always wins over
2910
- an in-flight autoplay wait. Flags beat WAYGRAPH_* and reset the checkbox
2911
- at process start (so --no-autoplay after a prior --autoplay session works).
2912
-
2913
- Manual watch (default): waygraph demo shopFlow
2914
- Auto-advance watch: waygraph demo shopFlow --autoplay
2915
- Same overlay on a chain: waygraph chain "login then nav-cart" --step
2916
- Unattended (no overlay): waygraph demo shopFlow --no-step
2917
- Onboarding (temp dir): npx waygraph try demo
2918
- Explore (temp dir): npx waygraph try auto
2919
-
2920
- Project path is optional - omit it to use the current directory.
2921
- You do not need a trailing "." (that is only shell shorthand for cwd).
2922
-
2923
- Other run flags (chain / demo / try) - flags beat WAYGRAPH_* env:
2924
- --base-url <url> base URL for relative page.goto()
2925
- --title <text> persistent overlay banner title
2926
- --video [dir] Playwright recordVideo (.webm); headless OK;
2927
- default dir: <project>/.waygraph-videos/
2928
-
2929
- Prefer flags (or npm scripts that pass them) over an ENV soup of WAYGRAPH_*.
2930
-
2931
- WAYGRAPH_JSON=1 reports for a script/agent instead of a human: no
2932
- --step overlay, no informational console.log, headless by default -
2933
- ONE JSON object on stdout, nothing else. Success: { ok: true, result,
2934
- steps: [{name, checkpoint, ms}, ...] }. Failure: { ok: false, failedAt,
2935
- stepIndex, totalSteps, error, steps (every EARLIER Block that
2936
- succeeded), screenshot (a PNG path captured at the exact moment of
2937
- failure - there's no live browser for anyone to glance at instead) }.
2938
- Exit code matches ok. Same session-reset boundaries and per-segment
2939
- mem-seeding --step already has.
2940
-
2941
- waygraph auto [project] Walks every *.block.ts in [project]
2942
- (default: cwd) and builds the app's own
2943
- state graph: Checkpoints are nodes, Blocks
2944
- are edges. A NavBlock's own runtime
2945
- "checkpoint" gives its Out tag for free
2946
- (In is always "*" - reachable from
2947
- anywhere); a regular defineBlock<In, Out>
2948
- gets its tags via the same lightweight
2949
- regex source-reading list/nav/check
2950
- already do, resolving each type identifier
2951
- back to its literal Checkpoint tag (a
2952
- union Out - a branching action Block -
2953
- becomes one edge per member). Prints the
2954
- graph as JSON by default, or --mermaid for
2955
- a stateDiagram-v2 text export. Reports how
2956
- many Blocks' Out couldn't be resolved
2957
- (skipped, not crashed).
2958
- waygraph try [demo|auto] One-shot saucedemo - "npx waygraph try demo"
2959
- (bare "try" = demo) or "npx waygraph try auto"
2960
- for the headed explorer. Copies into an OS
2961
- temp dir (never your cwd), npm installs there.
2962
- demo: chainFlow step-through then npm test.
2963
- auto: waygraph auto (Effect / MemNav picker).
2964
- Permanent scaffold: waygraph init <name>
2965
-
2966
- "check" is a secondary sweep, complementary to the always-on TypeScript
2967
- warning every regular Block's act() already gets (a struck-through
2968
- page.goto/reload/goBack/goForward, pointing at defineNavBlock) - useful
2969
- where nothing is watching in an editor (CI, generated code). Warning
2970
- only, never fails - see openspec/changes/nav-block-and-check/.
2971
-
2972
- "chain" needs no .flow.ts file - reference Blocks (by their own
2973
- runtime .name, or their export identifier) straight from the command
2974
- line, chained with "then", each optionally given a JSON payload for
2975
- whatever it requires:
2976
-
2977
- waygraph chain "login({\\"email\\":\\"a@b.com\\",\\"password\\":\\"x\\"}) then overview-metrics"
2978
-
2979
- "chain auto" builds the spec from waygraph auto's graph (requires zero
2980
- orphan Blocks - run waygraph check first):
2957
+ Primary (less is more):
2958
+ waygraph auto [project] Interactive explore (picker)
2959
+ --cli Terminal menu instead of browser panel
2960
+ --blocks <From> <To> Graph path-find From->To, then run
2961
+ --data '{...}' Mem seed (same as demo/run)
2962
+ waygraph demo [--blocks <flow|spec>] Watch with step overlay (QA path)
2963
+ --data '{...}' Mem seed JSON (or inline flow({...}))
2964
+ --auto-next Auto-advance steps (alias: --autoplay)
2965
+ --auto-play-video QA: --auto-next + --video (+ step)
2966
+ --title / --base-url
2967
+ waygraph run [--blocks <flow|spec>] Execute (no overlay unless --step)
2968
+ --data '{...}'
2969
+ --non-headless Show browser
2970
+ --video [dir] Record .webm
2981
2971
 
2982
- waygraph chain auto LoggedIn OrderComplete
2972
+ Also:
2973
+ waygraph list | nav | validate | check | graph | init <name>
2974
+ waygraph try [demo|auto] One-shot saucedemo in a temp dir
2983
2975
 
2984
- A Block with no requires needs no payload. One with several required
2985
- keys takes one JSON object keyed by each key's own name instead of a
2986
- flat payload.
2987
-
2988
- Runs in a child process rooted at "project" (its own waygraph/playwright,
2989
- never this CLI's) so a project's real Blocks resolve against its own
2990
- node_modules. Env vars still work (flags override them when both set):
2991
- WAYGRAPH_BASE_URL base URL for Blocks using relative page.goto()
2992
- WAYGRAPH_HEADED=1 show the browser instead of headless
2993
- WAYGRAPH_SLOWMO=ms slow down each Playwright action, for watching a run
2994
- WAYGRAPH_STEP=1 human-verification mode - one Block at a time, a
2995
- browser overlay shows/lets you edit that Block's
2996
- MemKeys, a "Run this step" button gates it, then a
2997
- highlight ring shows whatever its verify Traits
2998
- just confirmed, then "Next" before moving on.
2999
- Implies headed - stepping through headless defeats
3000
- the point.
3001
- WAYGRAPH_AUTOPLAY=1 step mode only - starting state for the panel's
3002
- own "Auto-advance" checkbox (advances on a timer
3003
- instead of waiting for clicks). The checkbox can
3004
- be flipped live mid-run - some steps auto, some
3005
- manual - and a manual click always wins over an
3006
- in-flight autoplay wait either way.
3007
- WAYGRAPH_AUTOPLAY_MS=ms delay between auto-advances (default 1800)
3008
- WAYGRAPH_FAST_BLOCKS=name1,name2
3009
- step mode only - these Blocks' own .name (e.g.
3010
- "login") blow past the overlay's added dwell -
3011
- ring pop, cursor travel, typing delay, and that
3012
- block's own gates - while the rest of the chain
3013
- keeps the full pace. Playwright's own slowMo is
3014
- untouched.
3015
- WAYGRAPH_CLEAR_SESSION=1
3016
- step mode only - clears cookies + storage before
3017
- every block AFTER the first. Off by default - a
3018
- normal chain (login -> dashboard) needs to STAY
3019
- authenticated across its own blocks. Only turn
3020
- this on for a chain that deliberately re-visits
3021
- an auth entry point as a fresh visitor each time
3022
- (e.g. "login" appearing more than once) - without
3023
- it the second visit is still authenticated, so
3024
- the app redirects away before the form renders.
3025
- WAYGRAPH_TITLE="..." step mode only - a persistent top banner
3026
- naming what this whole run is about (default
3027
- top-left; click the banner to cycle left /
3028
- center / right)
3029
- WAYGRAPH_TITLE_POS=left|center|right
3030
- initial banner position (default left); click
3031
- still cycles and remembers via localStorage
2976
+ Examples:
2977
+ waygraph auto --cli
2978
+ waygraph auto --blocks LoginPage OrderComplete
2979
+ waygraph demo --blocks shopFlow --auto-next
2980
+ waygraph demo --blocks cartBulkFlow --data '{"saucedemo.credentials":{...}}' --auto-play-video
2981
+ waygraph run --blocks "loginFlow then add-all-to-cart" --data '{...}' --video
2982
+ waygraph run --blocks shopFlow --non-headless --video
3032
2983
 
3033
- "project" defaults to the current directory.
2984
+ Aliases (compat): \`chain <spec>\` -> run --blocks; \`chain auto A B\` -> auto --blocks A B;
2985
+ --autoplay -> --auto-next. Prefer the primary verbs above.
3034
2986
 
3035
- Examples:
3036
- waygraph demo shopFlow
3037
- waygraph demo shopFlow --autoplay --title "Shop demo"
3038
- waygraph demo loginFlow --base-url https://www.saucedemo.com
3039
- waygraph chain "login then nav-cart" --step --no-autoplay
3040
- npm run demo # scaffold: waygraph demo exampleFlow
2987
+ Project path optional (defaults to cwd). Flags beat WAYGRAPH_* env.
3041
2988
  `);
3042
2989
  process.exit(0);
3043
2990
  }
@@ -3080,13 +3027,27 @@ async function main() {
3080
3027
  break;
3081
3028
  }
3082
3029
  case "run": {
3083
- const flowName = args[1];
3084
- if (!flowName) {
3085
- console.error("waygraph run: missing <flow>, e.g. waygraph run LoginFlow");
3030
+ const flags = parseRunFlags(args.slice(1));
3031
+ applyRunFlags(flags);
3032
+ const spec = resolveSpec(flags);
3033
+ if (!spec) {
3034
+ console.error('waygraph run: missing flow/spec — e.g. waygraph run --blocks shopFlow\n' +
3035
+ " Flags: --blocks --data --non-headless --video [dir] --step/--no-step");
3086
3036
  process.exit(1);
3087
3037
  }
3088
- const proj = resolve(args[2] ?? process.cwd());
3089
- await runFlow(proj, flowName);
3038
+ const proj = flags.blocks
3039
+ ? resolveProjectDir(flags, 0)
3040
+ : resolve(flags.positionals[1] ?? process.cwd());
3041
+ if (!existsSync(proj)) {
3042
+ console.error(`waygraph: no such directory: ${proj}`);
3043
+ process.exit(1);
3044
+ }
3045
+ if (!process.env.WAYGRAPH_BASE_URL) {
3046
+ const resolved = resolveBaseUrl(proj);
3047
+ if (resolved)
3048
+ process.env.WAYGRAPH_BASE_URL = resolved;
3049
+ }
3050
+ await runChain(proj, spec);
3090
3051
  break;
3091
3052
  }
3092
3053
  case "init": {
@@ -3094,6 +3055,7 @@ async function main() {
3094
3055
  break;
3095
3056
  }
3096
3057
  case "chain": {
3058
+ // Compat alias: chain auto A B -> auto --blocks A B; else -> run --blocks.
3097
3059
  const flags = parseRunFlags(args.slice(1));
3098
3060
  applyRunFlags(flags);
3099
3061
  const first = flags.positionals[0];
@@ -3102,7 +3064,7 @@ async function main() {
3102
3064
  const toTag = flags.positionals[2];
3103
3065
  if (!fromTag || !toTag) {
3104
3066
  console.error("waygraph chain auto: missing <fromCheckpoint> <toCheckpoint> [project]");
3105
- console.error(' e.g. waygraph chain auto LoggedIn OrderComplete');
3067
+ console.error(" prefer: waygraph auto --blocks LoggedIn OrderComplete");
3106
3068
  process.exit(1);
3107
3069
  }
3108
3070
  const proj = resolve(flags.positionals[3] ?? process.cwd());
@@ -3113,57 +3075,77 @@ async function main() {
3113
3075
  await runChainAuto(proj, fromTag, toTag);
3114
3076
  break;
3115
3077
  }
3116
- const spec = first;
3078
+ const spec = flags.blocks ?? first;
3117
3079
  if (!spec) {
3118
- console.error('waygraph chain: missing <spec>, e.g. waygraph chain "login({...}) then overview-metrics"\n' +
3119
- " or: waygraph chain auto <fromCheckpoint> <toCheckpoint> [project]");
3080
+ console.error('waygraph chain: missing <spec> — prefer: waygraph run --blocks "a then b"\n' +
3081
+ " or: waygraph auto --blocks <fromCheckpoint> <toCheckpoint>");
3120
3082
  process.exit(1);
3121
3083
  }
3122
- const proj = resolve(flags.positionals[1] ?? process.cwd());
3084
+ const proj = resolve(flags.blocks ? (flags.positionals[0] ?? process.cwd()) : (flags.positionals[1] ?? process.cwd()));
3123
3085
  if (!process.env.WAYGRAPH_BASE_URL) {
3124
3086
  const resolved = resolveBaseUrl(proj);
3125
3087
  if (resolved)
3126
3088
  process.env.WAYGRAPH_BASE_URL = resolved;
3127
3089
  }
3090
+ // If caller asked for step overlay, behave like demo; else like run.
3091
+ if (flags.step === true || process.env.WAYGRAPH_STEP === "1") {
3092
+ applyDemoDefaults(proj);
3093
+ }
3128
3094
  await runChain(proj, spec);
3129
3095
  break;
3130
3096
  }
3131
3097
  case "demo": {
3132
3098
  const flags = parseRunFlags(args.slice(1));
3133
- applyRunFlags(flags);
3134
- const spec = flags.positionals[0];
3099
+ applyRunFlags(flags, { allowAutoPlayVideo: true });
3100
+ const spec = resolveSpec(flags);
3135
3101
  if (!spec) {
3136
- console.error('waygraph demo: missing <flow|spec>, e.g. waygraph demo shopFlow\n' +
3137
- " Flags: --step/--no-step --autoplay/--no-autoplay --base-url <url> --title <text> --video [dir]");
3102
+ console.error('waygraph demo: missing flow/spec — e.g. waygraph demo --blocks shopFlow\n' +
3103
+ " Flags: --blocks --data --auto-next --auto-play-video --title --base-url --video");
3138
3104
  process.exit(1);
3139
3105
  }
3140
- const proj = resolve(flags.positionals[1] ?? process.cwd());
3106
+ const proj = flags.blocks
3107
+ ? resolveProjectDir(flags, 0)
3108
+ : resolve(flags.positionals[1] ?? process.cwd());
3141
3109
  if (!existsSync(proj)) {
3142
3110
  console.error(`waygraph: no such directory: ${proj}`);
3143
3111
  process.exit(1);
3144
3112
  }
3145
- // Friendly defaults after flags: STEP+headed on; BASE_URL from package/playwright.
3146
3113
  if (flags.step === undefined && process.env.WAYGRAPH_STEP === undefined) {
3147
3114
  process.env.WAYGRAPH_STEP = "1";
3148
3115
  }
3149
3116
  applyDemoDefaults(proj);
3150
3117
  console.error(`waygraph demo: STEP=${process.env.WAYGRAPH_STEP === "1" ? "on" : "off"}` +
3151
- ` AUTOPLAY=${process.env.WAYGRAPH_AUTOPLAY === "1" ? "on" : "off"}` +
3118
+ ` AUTO_NEXT=${process.env.WAYGRAPH_AUTOPLAY === "1" ? "on" : "off"}` +
3152
3119
  ` BASE_URL=${process.env.WAYGRAPH_BASE_URL ?? "(unset)"}` +
3120
+ (process.env.WAYGRAPH_VIDEO ? ` VIDEO=${process.env.WAYGRAPH_VIDEO}` : "") +
3153
3121
  (process.env.WAYGRAPH_TITLE ? ` TITLE=${JSON.stringify(process.env.WAYGRAPH_TITLE)}` : ""));
3154
3122
  await runChain(proj, spec);
3155
3123
  break;
3156
3124
  }
3157
3125
  case "graph":
3158
3126
  case "auto": {
3159
- const mermaid = args.includes("--mermaid");
3160
- const mapOnly = command === "graph" || args.includes("--map");
3161
- const cliPicker = args.includes("--cli");
3162
- const proj = resolve(args.find((a, i) => i >= 1 && !a.startsWith("--") && a !== "graph") ?? process.cwd());
3127
+ const flags = parseRunFlags(args.slice(1));
3128
+ applyRunFlags(flags);
3129
+ const mermaid = flags.mermaid === true;
3130
+ const mapOnly = command === "graph" || flags.map === true;
3131
+ const cliPicker = flags.cli === true;
3132
+ const proj = resolve(flags.positionals[0] ?? process.cwd());
3163
3133
  if (!existsSync(proj)) {
3164
3134
  console.error(`waygraph: no such directory: ${proj}`);
3165
3135
  process.exit(1);
3166
3136
  }
3137
+ // auto --blocks From To (graph path-find + run)
3138
+ if (command === "auto" && (flags.blocksFromTo || flags.blocks)) {
3139
+ if (flags.blocksFromTo) {
3140
+ const [fromTag, toTag] = flags.blocksFromTo;
3141
+ await runChainAuto(proj, fromTag, toTag);
3142
+ break;
3143
+ }
3144
+ console.error("waygraph auto --blocks expects <fromCheckpoint> <toCheckpoint>\n" +
3145
+ ' e.g. waygraph auto --blocks LoginPage OrderComplete\n' +
3146
+ ' for a hand-named chain use: waygraph run --blocks "a then b"');
3147
+ process.exit(1);
3148
+ }
3167
3149
  if (mapOnly || mermaid) {
3168
3150
  const orphans = await findOrphanBlocks(proj);
3169
3151
  const graph = await discoverGraph(proj);
@@ -3177,13 +3159,11 @@ async function main() {
3177
3159
  `${graph.skipped.length} Block(s) skipped (Out not resolvable), ` +
3178
3160
  `${orphans.length} orphan Block(s)`);
3179
3161
  if (orphans.length > 0) {
3180
- console.error("waygraph graph: orphan Blocks block chain auto shorthand - wire each into a .flow.ts (waygraph check)");
3162
+ console.error("waygraph graph: orphan Blocks block auto --blocks path-find - wire each into a .flow.ts (waygraph check)");
3181
3163
  }
3182
3164
  break;
3183
3165
  }
3184
- const baseURL = args.find((a, i) => args[i - 1] === "--base-url") ??
3185
- process.env.WAYGRAPH_BASE_URL ??
3186
- resolveBaseUrl(proj);
3166
+ const baseURL = flags.baseUrl ?? process.env.WAYGRAPH_BASE_URL ?? resolveBaseUrl(proj);
3187
3167
  await runAutoExplore(proj, baseURL ? { cli: cliPicker, baseURL } : { cli: cliPicker });
3188
3168
  break;
3189
3169
  }