libretto 0.5.3-experimental.5 → 0.5.3

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 (126) hide show
  1. package/README.md +114 -37
  2. package/README.template.md +160 -0
  3. package/dist/cli/cli.js +22 -97
  4. package/dist/cli/commands/browser.js +86 -59
  5. package/dist/cli/commands/deploy.js +148 -0
  6. package/dist/cli/commands/execution.js +218 -96
  7. package/dist/cli/commands/init.js +34 -29
  8. package/dist/cli/commands/logs.js +4 -5
  9. package/dist/cli/commands/shared.js +30 -29
  10. package/dist/cli/commands/snapshot.js +26 -39
  11. package/dist/cli/core/ai-config.js +21 -4
  12. package/dist/cli/core/api-snapshot-analyzer.js +15 -5
  13. package/dist/cli/core/browser.js +207 -37
  14. package/dist/cli/core/context.js +4 -1
  15. package/dist/cli/core/deploy-artifact.js +687 -0
  16. package/dist/cli/core/session-telemetry.js +434 -174
  17. package/dist/cli/core/session.js +21 -8
  18. package/dist/cli/core/snapshot-analyzer.js +14 -31
  19. package/dist/cli/core/snapshot-api-config.js +2 -6
  20. package/dist/cli/core/telemetry.js +20 -4
  21. package/dist/cli/framework/simple-cli.js +144 -43
  22. package/dist/cli/router.js +16 -21
  23. package/dist/cli/workers/run-integration-runtime.js +25 -45
  24. package/dist/cli/workers/run-integration-worker-protocol.js +3 -2
  25. package/dist/cli/workers/run-integration-worker.js +1 -4
  26. package/dist/index.d.ts +1 -2
  27. package/dist/index.js +13 -10
  28. package/dist/runtime/download/download.js +5 -1
  29. package/dist/runtime/extract/extract.js +11 -2
  30. package/dist/runtime/network/network.js +8 -1
  31. package/dist/runtime/recovery/agent.js +6 -2
  32. package/dist/runtime/recovery/errors.js +3 -1
  33. package/dist/runtime/recovery/recovery.js +3 -1
  34. package/dist/shared/condense-dom/condense-dom.js +17 -69
  35. package/dist/shared/config/config.d.ts +1 -9
  36. package/dist/shared/config/config.js +0 -18
  37. package/dist/shared/config/index.d.ts +2 -1
  38. package/dist/shared/config/index.js +0 -10
  39. package/dist/shared/debug/pause.js +9 -3
  40. package/dist/shared/dom-semantics.d.ts +8 -0
  41. package/dist/shared/dom-semantics.js +69 -0
  42. package/dist/shared/instrumentation/instrument.js +101 -5
  43. package/dist/shared/llm/ai-sdk-adapter.js +3 -1
  44. package/dist/shared/llm/client.js +3 -1
  45. package/dist/shared/logger/index.js +4 -1
  46. package/dist/shared/run/api.js +3 -1
  47. package/dist/shared/run/browser.js +47 -3
  48. package/dist/shared/state/session-state.d.ts +2 -1
  49. package/dist/shared/state/session-state.js +5 -2
  50. package/dist/shared/visualization/ghost-cursor.js +36 -14
  51. package/dist/shared/visualization/highlight.js +9 -6
  52. package/dist/shared/workflow/workflow.d.ts +18 -10
  53. package/dist/shared/workflow/workflow.js +50 -5
  54. package/package.json +14 -6
  55. package/scripts/generate-changelog.ts +132 -0
  56. package/scripts/postinstall.mjs +4 -3
  57. package/scripts/skills-libretto.mjs +2 -88
  58. package/scripts/summarize-evals.mjs +32 -10
  59. package/skills/libretto/SKILL.md +132 -62
  60. package/skills/libretto/references/action-logs.md +101 -0
  61. package/skills/libretto/references/auth-profiles.md +1 -2
  62. package/skills/libretto/references/code-generation-rules.md +176 -0
  63. package/skills/libretto/references/configuration-file-reference.md +53 -0
  64. package/skills/libretto/references/pages-and-page-targeting.md +1 -1
  65. package/skills/libretto/references/site-security-review.md +143 -0
  66. package/src/cli/cli.ts +23 -110
  67. package/src/cli/commands/browser.ts +94 -70
  68. package/src/cli/commands/deploy.ts +198 -0
  69. package/src/cli/commands/execution.ts +251 -111
  70. package/src/cli/commands/init.ts +37 -33
  71. package/src/cli/commands/logs.ts +7 -7
  72. package/src/cli/commands/shared.ts +36 -37
  73. package/src/cli/commands/snapshot.ts +44 -59
  74. package/src/cli/core/ai-config.ts +24 -4
  75. package/src/cli/core/api-snapshot-analyzer.ts +17 -6
  76. package/src/cli/core/browser.ts +260 -49
  77. package/src/cli/core/context.ts +7 -2
  78. package/src/cli/core/deploy-artifact.ts +938 -0
  79. package/src/cli/core/session-telemetry.ts +449 -197
  80. package/src/cli/core/session.ts +21 -7
  81. package/src/cli/core/snapshot-analyzer.ts +26 -46
  82. package/src/cli/core/snapshot-api-config.ts +170 -175
  83. package/src/cli/core/telemetry.ts +39 -4
  84. package/src/cli/framework/simple-cli.ts +281 -98
  85. package/src/cli/router.ts +15 -21
  86. package/src/cli/workers/run-integration-runtime.ts +35 -57
  87. package/src/cli/workers/run-integration-worker-protocol.ts +2 -1
  88. package/src/cli/workers/run-integration-worker.ts +1 -4
  89. package/src/index.ts +77 -67
  90. package/src/runtime/download/download.ts +62 -58
  91. package/src/runtime/download/index.ts +5 -5
  92. package/src/runtime/extract/extract.ts +71 -61
  93. package/src/runtime/network/index.ts +3 -3
  94. package/src/runtime/network/network.ts +99 -93
  95. package/src/runtime/recovery/agent.ts +217 -212
  96. package/src/runtime/recovery/errors.ts +107 -104
  97. package/src/runtime/recovery/index.ts +3 -3
  98. package/src/runtime/recovery/recovery.ts +38 -35
  99. package/src/shared/condense-dom/condense-dom.ts +27 -82
  100. package/src/shared/config/config.ts +0 -19
  101. package/src/shared/config/index.ts +0 -5
  102. package/src/shared/debug/pause.ts +57 -51
  103. package/src/shared/dom-semantics.ts +68 -0
  104. package/src/shared/instrumentation/errors.ts +64 -62
  105. package/src/shared/instrumentation/index.ts +5 -5
  106. package/src/shared/instrumentation/instrument.ts +339 -209
  107. package/src/shared/llm/ai-sdk-adapter.ts +58 -55
  108. package/src/shared/llm/client.ts +181 -174
  109. package/src/shared/llm/types.ts +39 -39
  110. package/src/shared/logger/index.ts +11 -4
  111. package/src/shared/logger/logger.ts +312 -306
  112. package/src/shared/logger/sinks.ts +118 -114
  113. package/src/shared/paths/paths.ts +50 -49
  114. package/src/shared/paths/repo-root.ts +17 -17
  115. package/src/shared/run/api.ts +5 -1
  116. package/src/shared/run/browser.ts +65 -3
  117. package/src/shared/state/index.ts +9 -9
  118. package/src/shared/state/session-state.ts +46 -43
  119. package/src/shared/visualization/ghost-cursor.ts +180 -149
  120. package/src/shared/visualization/highlight.ts +89 -86
  121. package/src/shared/visualization/index.ts +13 -13
  122. package/src/shared/workflow/workflow.ts +107 -30
  123. package/scripts/check-skills-sync.mjs +0 -23
  124. package/scripts/prepare-release.sh +0 -97
  125. package/skills/libretto/references/reverse-engineering-network-requests.md +0 -75
  126. package/skills/libretto/references/user-action-log.md +0 -31
@@ -1,19 +1,19 @@
1
1
  import type { Page } from "playwright";
2
2
 
3
3
  export type HighlightOptions = {
4
- color?: string;
5
- zIndex?: number;
4
+ color?: string;
5
+ zIndex?: number;
6
6
  };
7
7
 
8
8
  const HIGHLIGHT_DEFAULTS = {
9
- color: "rgba(59, 130, 246, 0.25)",
10
- zIndex: 2147483645,
9
+ color: "rgba(59, 130, 246, 0.25)",
10
+ zIndex: 2147483645,
11
11
  };
12
12
 
13
13
  const LAYER_ID = "__libretto_highlight_layer__";
14
14
 
15
15
  function buildHighlightInitScript(opts: { zIndex: number }): string {
16
- return `
16
+ return `
17
17
  (function() {
18
18
  if (document.getElementById("${LAYER_ID}")) return;
19
19
  var el = document.createElement("div");
@@ -27,59 +27,59 @@ function buildHighlightInitScript(opts: { zIndex: number }): string {
27
27
  const installedPages = new WeakSet<Page>();
28
28
 
29
29
  export async function ensureHighlightLayer(
30
- page: Page,
31
- options?: HighlightOptions,
30
+ page: Page,
31
+ options?: HighlightOptions,
32
32
  ): Promise<void> {
33
- const existingOpts = (page as any).__librettoHighlightOpts as
34
- | { color: string; zIndex: number }
35
- | undefined;
36
- const zIndex =
37
- options?.zIndex ?? existingOpts?.zIndex ?? HIGHLIGHT_DEFAULTS.zIndex;
38
- const initScript = buildHighlightInitScript({ zIndex });
39
-
40
- if (!installedPages.has(page)) {
41
- installedPages.add(page);
42
- await page.addInitScript({ content: initScript });
43
- }
44
-
45
- // Store/refresh options for later.
46
- (page as any).__librettoHighlightOpts = {
47
- color: options?.color ?? existingOpts?.color ?? HIGHLIGHT_DEFAULTS.color,
48
- zIndex,
49
- };
50
-
51
- // Re-run in-page installer so overlays recover after page.setContent() or DOM resets.
52
- try {
53
- await page.evaluate(new Function(initScript) as () => void);
54
- } catch {
55
- // Page may not be ready
56
- }
33
+ const existingOpts = (page as any).__librettoHighlightOpts as
34
+ | { color: string; zIndex: number }
35
+ | undefined;
36
+ const zIndex =
37
+ options?.zIndex ?? existingOpts?.zIndex ?? HIGHLIGHT_DEFAULTS.zIndex;
38
+ const initScript = buildHighlightInitScript({ zIndex });
39
+
40
+ if (!installedPages.has(page)) {
41
+ installedPages.add(page);
42
+ await page.addInitScript({ content: initScript });
43
+ }
44
+
45
+ // Store/refresh options for later.
46
+ (page as any).__librettoHighlightOpts = {
47
+ color: options?.color ?? existingOpts?.color ?? HIGHLIGHT_DEFAULTS.color,
48
+ zIndex,
49
+ };
50
+
51
+ // Re-run in-page installer so overlays recover after page.setContent() or DOM resets.
52
+ try {
53
+ await page.evaluate(new Function(initScript) as () => void);
54
+ } catch {
55
+ // Page may not be ready
56
+ }
57
57
  }
58
58
 
59
59
  export type ShowHighlightParams = {
60
- box: { x: number; y: number; width: number; height: number };
61
- label?: string;
62
- color?: string;
63
- durationMs?: number;
60
+ box: { x: number; y: number; width: number; height: number };
61
+ label?: string;
62
+ color?: string;
63
+ durationMs?: number;
64
64
  };
65
65
 
66
66
  export async function showHighlight(
67
- page: Page,
68
- params: ShowHighlightParams,
67
+ page: Page,
68
+ params: ShowHighlightParams,
69
69
  ): Promise<void> {
70
- const opts = (page as any).__librettoHighlightOpts ?? HIGHLIGHT_DEFAULTS;
71
- const color = params.color ?? opts.color;
72
- const durationMs = params.durationMs ?? 350;
73
-
74
- try {
75
- await page.evaluate(
76
- ({ layerId, box, color, label, durationMs }) => {
77
- const layer = document.getElementById(layerId);
78
- if (!layer) return;
79
-
80
- const rect = document.createElement("div");
81
- rect.className = "__libretto_highlight_rect__";
82
- rect.style.cssText = `
70
+ const opts = (page as any).__librettoHighlightOpts ?? HIGHLIGHT_DEFAULTS;
71
+ const color = params.color ?? opts.color;
72
+ const durationMs = params.durationMs ?? 350;
73
+
74
+ try {
75
+ await page.evaluate(
76
+ ({ layerId, box, color, label, durationMs }) => {
77
+ const layer = document.getElementById(layerId);
78
+ if (!layer) return;
79
+
80
+ const rect = document.createElement("div");
81
+ rect.className = "__libretto_highlight_rect__";
82
+ rect.style.cssText = `
83
83
  position:absolute;
84
84
  left:${box.x}px;
85
85
  top:${box.y}px;
@@ -93,10 +93,10 @@ export async function showHighlight(
93
93
  opacity:1;
94
94
  `;
95
95
 
96
- if (label) {
97
- const labelEl = document.createElement("div");
98
- labelEl.textContent = label;
99
- labelEl.style.cssText = `
96
+ if (label) {
97
+ const labelEl = document.createElement("div");
98
+ labelEl.textContent = label;
99
+ labelEl.style.cssText = `
100
100
  position:absolute;
101
101
  top:-22px;
102
102
  left:0;
@@ -108,39 +108,42 @@ export async function showHighlight(
108
108
  white-space:nowrap;
109
109
  pointer-events:none;
110
110
  `;
111
- rect.appendChild(labelEl);
112
- }
113
-
114
- layer.appendChild(rect);
115
-
116
- // Auto-fade after duration
117
- setTimeout(() => {
118
- rect.style.opacity = "0";
119
- setTimeout(() => rect.remove(), 250);
120
- }, durationMs);
121
- },
122
- {
123
- layerId: LAYER_ID,
124
- box: params.box,
125
- color,
126
- label: params.label,
127
- durationMs,
128
- },
129
- );
130
- } catch {
131
- // Best-effort
132
- }
111
+ rect.appendChild(labelEl);
112
+ }
113
+
114
+ layer.appendChild(rect);
115
+
116
+ // Auto-fade after duration
117
+ setTimeout(() => {
118
+ rect.style.opacity = "0";
119
+ setTimeout(() => rect.remove(), 250);
120
+ }, durationMs);
121
+ },
122
+ {
123
+ layerId: LAYER_ID,
124
+ box: params.box,
125
+ color,
126
+ label: params.label,
127
+ durationMs,
128
+ },
129
+ );
130
+ } catch {
131
+ // Best-effort
132
+ }
133
133
  }
134
134
 
135
135
  export async function clearHighlights(page: Page): Promise<void> {
136
- try {
137
- await page.evaluate(({ layerId }) => {
138
- const layer = document.getElementById(layerId);
139
- if (!layer) return;
140
- const rects = layer.querySelectorAll(".__libretto_highlight_rect__");
141
- rects.forEach((r) => r.remove());
142
- }, { layerId: LAYER_ID });
143
- } catch {
144
- // Best-effort
145
- }
136
+ try {
137
+ await page.evaluate(
138
+ ({ layerId }) => {
139
+ const layer = document.getElementById(layerId);
140
+ if (!layer) return;
141
+ const rects = layer.querySelectorAll(".__libretto_highlight_rect__");
142
+ rects.forEach((r) => r.remove());
143
+ },
144
+ { layerId: LAYER_ID },
145
+ );
146
+ } catch {
147
+ // Best-effort
148
+ }
146
149
  }
@@ -1,18 +1,18 @@
1
1
  export {
2
- ensureGhostCursor,
3
- moveGhostCursor,
4
- moveGhostCursorWithDistance,
5
- ghostClick,
6
- hideGhostCursor,
7
- getGhostCursorPosition,
8
- type GhostCursorOptions,
9
- type GhostCursorStyle,
2
+ ensureGhostCursor,
3
+ moveGhostCursor,
4
+ moveGhostCursorWithDistance,
5
+ ghostClick,
6
+ hideGhostCursor,
7
+ getGhostCursorPosition,
8
+ type GhostCursorOptions,
9
+ type GhostCursorStyle,
10
10
  } from "./ghost-cursor.js";
11
11
 
12
12
  export {
13
- ensureHighlightLayer,
14
- showHighlight,
15
- clearHighlights,
16
- type HighlightOptions,
17
- type ShowHighlightParams,
13
+ ensureHighlightLayer,
14
+ showHighlight,
15
+ clearHighlights,
16
+ type HighlightOptions,
17
+ type ShowHighlightParams,
18
18
  } from "./highlight.js";
@@ -3,40 +3,117 @@ import type { MinimalLogger } from "../logger/logger.js";
3
3
 
4
4
  export const LIBRETTO_WORKFLOW_BRAND = Symbol.for("libretto.workflow");
5
5
 
6
- export type LibrettoWorkflowMetadata = {};
7
-
8
- export type LibrettoWorkflowContext<S = {}> = {
9
- page: Page;
10
- logger: MinimalLogger;
11
- services: S;
6
+ export type LibrettoWorkflowContext = {
7
+ session: string;
8
+ page: Page;
9
+ logger: MinimalLogger;
12
10
  };
13
11
 
14
- export type LibrettoWorkflowHandler<Input = unknown, Output = unknown, S = {}> = (
15
- ctx: LibrettoWorkflowContext<S>,
16
- input: Input,
12
+ export type LibrettoWorkflowHandler<Input = unknown, Output = unknown> = (
13
+ ctx: LibrettoWorkflowContext,
14
+ input: Input,
17
15
  ) => Promise<Output>;
18
16
 
19
- export class LibrettoWorkflow<Input = unknown, Output = unknown, S = {}> {
20
- public readonly [LIBRETTO_WORKFLOW_BRAND] = true;
21
- public readonly metadata: LibrettoWorkflowMetadata;
22
- private readonly handler: LibrettoWorkflowHandler<Input, Output, S>;
23
-
24
- constructor(
25
- metadata: LibrettoWorkflowMetadata,
26
- handler: LibrettoWorkflowHandler<Input, Output, S>,
27
- ) {
28
- this.metadata = metadata;
29
- this.handler = handler;
30
- }
31
-
32
- async run(ctx: LibrettoWorkflowContext<S>, input: Input): Promise<Output> {
33
- return this.handler(ctx, input);
34
- }
17
+ export class LibrettoWorkflow<Input = unknown, Output = unknown> {
18
+ public readonly [LIBRETTO_WORKFLOW_BRAND] = true;
19
+ public readonly name: string;
20
+ private readonly handler: LibrettoWorkflowHandler<Input, Output>;
21
+
22
+ constructor(
23
+ name: string,
24
+ handler: LibrettoWorkflowHandler<Input, Output>,
25
+ ) {
26
+ this.name = name;
27
+ this.handler = handler;
28
+ }
29
+
30
+ async run(ctx: LibrettoWorkflowContext, input: Input): Promise<Output> {
31
+ return this.handler(ctx, input);
32
+ }
33
+ }
34
+
35
+ export type ExportedLibrettoWorkflow = {
36
+ readonly [LIBRETTO_WORKFLOW_BRAND]: true;
37
+ readonly name: string;
38
+ run: (ctx: LibrettoWorkflowContext, input: unknown) => Promise<unknown>;
39
+ };
40
+
41
+ type WorkflowModuleExports = Record<string, unknown>;
42
+
43
+ // Use the workflow brand instead of `instanceof` so imported workflows are
44
+ // still recognized after loading the integration module dynamically.
45
+ export function isLibrettoWorkflow(
46
+ value: unknown,
47
+ ): value is ExportedLibrettoWorkflow {
48
+ if (!value || typeof value !== "object") return false;
49
+ const candidate = value as Record<PropertyKey, unknown>;
50
+ return (
51
+ candidate[LIBRETTO_WORKFLOW_BRAND] === true &&
52
+ typeof candidate.name === "string" &&
53
+ typeof candidate.run === "function"
54
+ );
55
+ }
56
+
57
+ function addWorkflowOrThrow(
58
+ workflowsByName: Map<string, ExportedLibrettoWorkflow>,
59
+ value: unknown,
60
+ ): void {
61
+ if (!isLibrettoWorkflow(value)) return;
62
+
63
+ // Re-exporting the same workflow object is fine, but two distinct workflow
64
+ // instances cannot claim the same runtime name.
65
+ const existing = workflowsByName.get(value.name);
66
+ if (existing && existing !== value) {
67
+ throw new Error(
68
+ `Duplicate workflow name: "${value.name}". Each workflow() call must use a unique name.`,
69
+ );
70
+ }
71
+
72
+ workflowsByName.set(value.name, value);
73
+ }
74
+
75
+ export function getWorkflowsFromModuleExports(
76
+ moduleExports: WorkflowModuleExports,
77
+ ): ExportedLibrettoWorkflow[] {
78
+ const workflowsByName = new Map<string, ExportedLibrettoWorkflow>();
79
+
80
+ for (const [exportName, value] of Object.entries(moduleExports)) {
81
+ if (exportName === "workflows" && value && typeof value === "object") {
82
+ // Support both `export const workflows = workflow(...)` and
83
+ // `export const workflows = { myWorkflow }`.
84
+ if (isLibrettoWorkflow(value)) {
85
+ addWorkflowOrThrow(workflowsByName, value);
86
+ } else {
87
+ for (const nestedValue of Object.values(
88
+ value as Record<string, unknown>,
89
+ )) {
90
+ addWorkflowOrThrow(workflowsByName, nestedValue);
91
+ }
92
+ }
93
+ continue;
94
+ }
95
+
96
+ addWorkflowOrThrow(workflowsByName, value);
97
+ }
98
+
99
+ return [...workflowsByName.values()];
100
+ }
101
+
102
+ export function getWorkflowFromModuleExports(
103
+ moduleExports: WorkflowModuleExports,
104
+ workflowName: string,
105
+ ): ExportedLibrettoWorkflow | null {
106
+ for (const workflow of getWorkflowsFromModuleExports(moduleExports)) {
107
+ if (workflow.name === workflowName) {
108
+ return workflow;
109
+ }
110
+ }
111
+ return null;
35
112
  }
36
113
 
37
- export function workflow<Input = unknown, Output = unknown, S = {}>(
38
- metadata: LibrettoWorkflowMetadata,
39
- handler: LibrettoWorkflowHandler<Input, Output, S>,
40
- ): LibrettoWorkflow<Input, Output, S> {
41
- return new LibrettoWorkflow(metadata, handler);
114
+ export function workflow<Input = unknown, Output = unknown>(
115
+ name: string,
116
+ handler: LibrettoWorkflowHandler<Input, Output>,
117
+ ): LibrettoWorkflow<Input, Output> {
118
+ return new LibrettoWorkflow(name, handler);
42
119
  }
@@ -1,23 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { dirname, join } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
-
6
- import { compareSkillDirs, SKILL_DIRS } from "./skills-libretto.mjs";
7
-
8
- const __dirname = dirname(fileURLToPath(import.meta.url));
9
- const repoRoot = join(__dirname, "..");
10
- const result = compareSkillDirs(repoRoot);
11
-
12
- if (result.ok) {
13
- console.log(`libretto: verified identical skill mirrors across ${SKILL_DIRS.join(", ")}`);
14
- process.exit(0);
15
- }
16
-
17
- console.error("libretto: skill directories must be identical:");
18
- for (const issue of result.issues) {
19
- console.error(`- ${issue}`);
20
- }
21
- console.error("");
22
- console.error("Run `pnpm i` to resync the mirrors in this repository.");
23
- process.exit(1);
@@ -1,97 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- usage() {
5
- cat <<'EOF'
6
- Usage: scripts/prepare-release.sh [patch|minor|major]
7
-
8
- Creates a release PR branch from main, bumps package.json, pushes the branch,
9
- and opens a pull request targeting main.
10
- EOF
11
- }
12
-
13
- bump="${1:-patch}"
14
-
15
- case "$bump" in
16
- patch|minor|major)
17
- ;;
18
- -h|--help|help)
19
- usage
20
- exit 0
21
- ;;
22
- *)
23
- echo "Invalid bump type: $bump" >&2
24
- usage >&2
25
- exit 1
26
- ;;
27
- esac
28
-
29
- if ! command -v gh >/dev/null 2>&1; then
30
- echo "gh CLI is required." >&2
31
- exit 1
32
- fi
33
-
34
- if [ -n "$(git status --porcelain)" ]; then
35
- echo "Working tree must be clean before preparing a release." >&2
36
- exit 1
37
- fi
38
-
39
- current_branch="$(git branch --show-current)"
40
- if [ "$current_branch" != "main" ]; then
41
- echo "Switching from $current_branch to main."
42
- fi
43
-
44
- git fetch origin
45
- git checkout main
46
- git pull --ff-only origin main
47
-
48
- pnpm install --frozen-lockfile
49
- pnpm type-check
50
- pnpm test
51
-
52
- current_version="$(node -p "require('./package.json').version")"
53
- next_version="$(node -e '
54
- const [major, minor, patch] = process.argv[1].split(".").map(Number)
55
- const bump = process.argv[2]
56
-
57
- let next
58
- if (bump === "major") next = [major + 1, 0, 0]
59
- else if (bump === "minor") next = [major, minor + 1, 0]
60
- else next = [major, minor, patch + 1]
61
-
62
- process.stdout.write(next.join("."))
63
- ' "$current_version" "$bump")"
64
- branch_name="tk-release-v${next_version}"
65
-
66
- if git show-ref --verify --quiet "refs/heads/${branch_name}"; then
67
- echo "Local branch ${branch_name} already exists." >&2
68
- exit 1
69
- fi
70
-
71
- if git ls-remote --exit-code --heads origin "${branch_name}" >/dev/null 2>&1; then
72
- echo "Remote branch ${branch_name} already exists." >&2
73
- exit 1
74
- fi
75
-
76
- npm version "$next_version" --no-git-tag-version >/dev/null
77
-
78
- git checkout -b "$branch_name"
79
- git add package.json
80
- git commit -m "release: v${next_version}"
81
- git push -u origin "$branch_name"
82
-
83
- gh pr create \
84
- --base main \
85
- --head "$branch_name" \
86
- --title "release: v${next_version}" \
87
- --body "$(cat <<EOF
88
- ## Summary
89
-
90
- - release libretto v${next_version}
91
-
92
- ## Verification
93
-
94
- - pnpm type-check
95
- - pnpm test
96
- EOF
97
- )"
@@ -1,75 +0,0 @@
1
- # Reverse Engineering Network Requests
2
-
3
- Use this reference when the user wants to turn a browser workflow into direct network requests or decide whether direct request replay is the right extraction path.
4
-
5
- This is the default approach for new integrations when the site exposes a clear and stable HTTP request path.
6
-
7
- ## When to Use This
8
-
9
- - The page clearly loads or submits data through HTTP requests.
10
- - The user can perform the workflow manually in a headed browser.
11
- - Replaying the request is likely faster or more stable than reproducing every UI action.
12
- - You need to decide whether direct browser `fetch` is safe enough to try.
13
- - Fall back to browser automation when the request path is unclear, too dynamic, or blocked by anti-bot systems.
14
-
15
- ## Choosing the Capture Path
16
-
17
- - Prefer direct browser `fetch` when the site exposes a stable endpoint, `window.fetch` appears unpatched, and the response returns the data or action result you need.
18
- - Prefer passive capture plus UI automation when the site appears to wrap `fetch`, shows challenge pages or other bot-protection signals, or only exposes useful requests through normal page interaction.
19
- - Fall back to DOM extraction when the page is server-rendered or the captured responses are not usable.
20
-
21
- ## Common Bot-Protection Signals
22
-
23
- These are examples, not a complete checklist.
24
-
25
- | Cookie Pattern | Common Association |
26
- | --- | --- |
27
- | `_abck` | Akamai Bot Manager |
28
- | `_px*` | PerimeterX / HUMAN |
29
- | `datadome` | DataDome |
30
- | `cf_clearance` | Cloudflare |
31
- | `_imp_apg_r_*` | Shape Security / F5 |
32
- | `x-kpsdk-*` | Kasada |
33
-
34
- - Unknown cookies can still be relevant if they look like telemetry, fingerprint, or signed security tokens.
35
- - If cookies suggest bot protection, also check for challenge pages, early-loading security scripts, and wrapped `fetch` or XHR APIs before trying direct request replay.
36
-
37
- ## Workflow
38
-
39
- - Open the page in headed mode.
40
- - Let the user perform the relevant workflow manually.
41
- - Read the network log after the relevant step.
42
- - Identify the smallest set of requests that actually carries the data or performs the action.
43
- - Before replaying a request, check whether direct browser `fetch` looks safe to try.
44
- - Look for challenge pages, obvious bot-protection signals, or wrapped `fetch` and XHR APIs.
45
- - Confirm with the user before replaying any request that could mutate data.
46
- - If direct browser `fetch` looks safe, recreate a key request with `page.evaluate(() => fetch(...))`.
47
- - If direct browser `fetch` is unsafe or does not work, keep using captured responses and UI automation for the triggering steps.
48
- - Recreate the working request path in code outside Libretto.
49
- - Verify the resulting workflow with `npx libretto run ...`.
50
-
51
- ## Commands
52
-
53
- ```bash
54
- npx libretto open https://target.example.com --headed
55
- npx libretto network --last 20
56
- npx libretto network --method POST --last 20
57
- npx libretto network --filter 'referral|patient|search'
58
- npx libretto exec "return await networkLog({ method: 'POST', last: 10 })"
59
- npx libretto exec "return await page.evaluate(() => ({ fetch: window.fetch.toString(), xhrOpen: XMLHttpRequest.prototype.open.toString() }))"
60
- npx libretto exec "
61
- return await page.evaluate(async () => {
62
- const resp = await fetch('/api/example', { method: 'GET' });
63
- return await resp.text();
64
- });
65
- "
66
- ```
67
-
68
- ## Notes
69
-
70
- - Start with the request that returns the data you need, not every request on the page.
71
- - Prefer captured requests over guessing payload shape.
72
- - Treat every replayed request, including `GET`, as potentially side-effectful until proven otherwise.
73
- - `page.evaluate(() => fetch(...))` can recreate either fetch-based or XHR-based endpoints because you are issuing a new browser-context request.
74
- - If a captured endpoint depends on signatures, rotating tokens, or opaque response formats, use Playwright to trigger it and capture the result instead of forcing direct replay.
75
- - If the request format is opaque, highly dynamic, or heavily defended, fall back to UI automation for that part.
@@ -1,31 +0,0 @@
1
- # User Action Log
2
-
3
- Use this reference when the user performs steps manually in a headed Libretto session and you want to incorporate those steps into a workflow.
4
-
5
- ## When to Use This
6
-
7
- - The user demonstrates the workflow in the browser window.
8
- - You want to know what they clicked, typed, or selected.
9
- - You need to reconcile manual user actions with captured network requests.
10
-
11
- ## Workflow
12
-
13
- - Open the session in headed mode.
14
- - Ask the user to perform the workflow.
15
- - Read the user action log after they finish.
16
- - Use the log to identify the important transitions in the workflow.
17
- - Combine the action log with `snapshot` or `network` when the log alone is not enough.
18
-
19
- ## Commands
20
-
21
- ```bash
22
- npx libretto actions --source user --last 20
23
- npx libretto actions --source user --filter 'button|input|select'
24
- npx libretto exec "return await actionLog({ source: 'user', last: 10 })"
25
- ```
26
-
27
- ## Notes
28
-
29
- - The action log is most useful for reconstructing the sequence of a workflow, not for discovering every selector you need.
30
- - If the user performed relevant manual steps, read the action log before writing or revising the workflow code.
31
- - Use the action log to anchor your understanding, then inspect the current page state with `snapshot` or `exec`.