libretto 0.6.16 → 0.6.17

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 (40) hide show
  1. package/dist/cli/cli.js +32 -13
  2. package/dist/cli/commands/browser.js +2 -2
  3. package/dist/cli/commands/execution.js +1 -1
  4. package/dist/cli/commands/search.js +69 -0
  5. package/dist/cli/commands/update.js +122 -0
  6. package/dist/cli/core/context.js +4 -0
  7. package/dist/cli/core/daemon/daemon.js +3 -0
  8. package/dist/cli/core/experiments.js +14 -1
  9. package/dist/cli/core/providers/index.js +5 -1
  10. package/dist/cli/core/providers/steel.js +56 -0
  11. package/dist/cli/core/session-telemetry.js +143 -7
  12. package/dist/cli/core/skill-version.js +1 -0
  13. package/dist/cli/router.js +14 -3
  14. package/dist/shared/html-search/search-html.d.ts +9 -0
  15. package/dist/shared/html-search/search-html.js +46 -0
  16. package/dist/shared/html-search/search-html.spec.d.ts +2 -0
  17. package/dist/shared/html-search/search-html.spec.js +57 -0
  18. package/docs/releasing.md +3 -9
  19. package/package.json +2 -2
  20. package/scripts/generate-changelog.ts +207 -12
  21. package/skills/libretto/SKILL.md +22 -15
  22. package/skills/libretto/references/code-generation-rules.md +2 -2
  23. package/skills/libretto/references/configuration-file-reference.md +3 -2
  24. package/skills/libretto-readonly/SKILL.md +1 -1
  25. package/src/cli/cli.ts +38 -13
  26. package/src/cli/commands/browser.ts +2 -3
  27. package/src/cli/commands/execution.ts +1 -1
  28. package/src/cli/commands/search.ts +74 -0
  29. package/src/cli/commands/update.ts +149 -0
  30. package/src/cli/core/context.ts +4 -0
  31. package/src/cli/core/daemon/daemon.ts +3 -0
  32. package/src/cli/core/experiments.ts +15 -1
  33. package/src/cli/core/providers/index.ts +5 -1
  34. package/src/cli/core/providers/steel.ts +75 -0
  35. package/src/cli/core/session-telemetry.ts +176 -13
  36. package/src/cli/core/skill-version.ts +1 -1
  37. package/src/cli/core/telemetry.ts +19 -3
  38. package/src/cli/router.ts +13 -2
  39. package/src/shared/html-search/search-html.spec.ts +65 -0
  40. package/src/shared/html-search/search-html.ts +75 -0
package/src/cli/router.ts CHANGED
@@ -7,12 +7,14 @@ import { experimentsCommand } from "./commands/experiments.js";
7
7
  import { setupCommand } from "./commands/setup.js";
8
8
  import { statusCommand } from "./commands/status.js";
9
9
  import { snapshotCommand } from "./commands/snapshot.js";
10
+ import { searchCommand } from "./commands/search.js";
11
+ import { updateCommand } from "./commands/update.js";
10
12
  import { SimpleCLI } from "affordance";
11
13
 
12
14
  export const cliRoutes = {
13
15
  ...browserCommands,
14
16
  cloud: SimpleCLI.group({
15
- description: "Libretto Cloud commands",
17
+ description: "Deploy workflows and manage hosted Libretto",
16
18
  routes: {
17
19
  deploy: deployCommand,
18
20
  auth: authCommands,
@@ -21,11 +23,20 @@ export const cliRoutes = {
21
23
  }),
22
24
  experiments: experimentsCommand,
23
25
  ...executionCommands,
26
+ search: searchCommand,
24
27
  setup: setupCommand,
25
28
  status: statusCommand,
26
29
  snapshot: snapshotCommand,
30
+ update: updateCommand,
27
31
  };
28
32
 
29
33
  export function createCLIApp() {
30
- return SimpleCLI.define("libretto", cliRoutes);
34
+ return SimpleCLI.define("libretto", cliRoutes, {
35
+ appendHelpText: [
36
+ "Options:",
37
+ " --session <name> Required for session-scoped commands",
38
+ " -h, --help",
39
+ " -v, --version",
40
+ ].join("\n"),
41
+ });
31
42
  }
@@ -0,0 +1,65 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { formatHtmlForSearch, searchFormattedHtml } from "./search-html.js";
3
+
4
+ describe("HTML search", () => {
5
+ test("formats condensed HTML before searching", () => {
6
+ const formatted = formatHtmlForSearch(
7
+ '<!doctype html><html><body><main><p data-testid="target">Needle</p></main></body></html>',
8
+ );
9
+
10
+ expect(formatted).toContain('<p data-testid="target">');
11
+ expect(formatted).toContain("Needle");
12
+ });
13
+
14
+ test("returns merged matching regions with context", () => {
15
+ const formatted = [
16
+ "<html>",
17
+ "<body>",
18
+ "<main>",
19
+ "<section>",
20
+ "<h1>Heading</h1>",
21
+ '<p class="target">Needle</p>',
22
+ "<p>More content</p>",
23
+ "</section>",
24
+ "</main>",
25
+ "</body>",
26
+ "</html>",
27
+ ].join("\n");
28
+
29
+ const matches = searchFormattedHtml(formatted, "Needle", 2);
30
+
31
+ expect(matches).toEqual([
32
+ {
33
+ startLine: 4,
34
+ endLine: 8,
35
+ lines: [
36
+ "<section>",
37
+ "<h1>Heading</h1>",
38
+ '<p class="target">Needle</p>',
39
+ "<p>More content</p>",
40
+ "</section>",
41
+ ],
42
+ },
43
+ ]);
44
+ });
45
+
46
+ test("limits matching regions before adding context", () => {
47
+ const formatted = Array.from(
48
+ { length: 12 },
49
+ (_value, index) => `<p>Needle ${index}</p>`,
50
+ ).join("\n");
51
+
52
+ const matches = searchFormattedHtml(formatted, "Needle", 0, 8);
53
+
54
+ expect(matches).toEqual([
55
+ {
56
+ startLine: 1,
57
+ endLine: 8,
58
+ lines: Array.from(
59
+ { length: 8 },
60
+ (_value, index) => `<p>Needle ${index}</p>`,
61
+ ),
62
+ },
63
+ ]);
64
+ });
65
+ });
@@ -0,0 +1,75 @@
1
+ import { condenseDom } from "../condense-dom/condense-dom.js";
2
+
3
+ const DEFAULT_CONTEXT_LINES = 4;
4
+ const DEFAULT_MATCH_LIMIT = 8;
5
+
6
+ export type SearchHtmlMatch = {
7
+ startLine: number;
8
+ endLine: number;
9
+ lines: string[];
10
+ };
11
+
12
+ export function formatHtmlForSearch(html: string): string {
13
+ const condensed = condenseDom(html).html;
14
+ const separated = condensed
15
+ .replace(/>\s+</g, ">\n<")
16
+ .replace(/(<[^/!][^>]*>)([^<\n][\s\S]*?)(<\/[^>]+>)/g, "$1\n$2\n$3");
17
+
18
+ const lines = separated
19
+ .split("\n")
20
+ .map((line) => line.trim())
21
+ .filter((line) => line.length > 0);
22
+
23
+ let indent = 0;
24
+ return lines
25
+ .map((line) => {
26
+ if (/^<\//.test(line)) indent = Math.max(0, indent - 1);
27
+ const formatted = `${" ".repeat(indent)}${line}`;
28
+ if (isOpeningTag(line)) indent += 1;
29
+ return formatted;
30
+ })
31
+ .join("\n");
32
+ }
33
+
34
+ export function searchFormattedHtml(
35
+ formattedHtml: string,
36
+ pattern: string,
37
+ contextLines = DEFAULT_CONTEXT_LINES,
38
+ matchLimit = DEFAULT_MATCH_LIMIT,
39
+ ): SearchHtmlMatch[] {
40
+ const regex = new RegExp(pattern);
41
+ const lines = formattedHtml.split("\n");
42
+ const matchingIndexes = lines
43
+ .map((line, index) => (regex.test(line) ? index : -1))
44
+ .filter((index) => index >= 0)
45
+ .slice(0, matchLimit);
46
+
47
+ const matches: SearchHtmlMatch[] = [];
48
+ for (const matchingIndex of matchingIndexes) {
49
+ const startLine = Math.max(0, matchingIndex - contextLines);
50
+ const endLine = Math.min(lines.length - 1, matchingIndex + contextLines);
51
+ const previous = matches.at(-1);
52
+ if (previous && startLine <= previous.endLine) {
53
+ previous.endLine = Math.max(previous.endLine, endLine + 1);
54
+ previous.lines = lines.slice(previous.startLine - 1, previous.endLine);
55
+ continue;
56
+ }
57
+ matches.push({
58
+ startLine: startLine + 1,
59
+ endLine: endLine + 1,
60
+ lines: lines.slice(startLine, endLine + 1),
61
+ });
62
+ }
63
+ return matches;
64
+ }
65
+
66
+ function isOpeningTag(line: string): boolean {
67
+ return (
68
+ /^<[^/!?][^>]*>$/.test(line) &&
69
+ !/\/>$/.test(line) &&
70
+ !/^<(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)\b/i.test(
71
+ line,
72
+ ) &&
73
+ !/^<[^>]+>.*<\/[^>]+>$/.test(line)
74
+ );
75
+ }