agentopology 0.2.5 → 0.3.0

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 (38) hide show
  1. package/README.md +69 -3
  2. package/dist/bindings/claude-code.d.ts.map +1 -1
  3. package/dist/bindings/claude-code.js +100 -2
  4. package/dist/bindings/claude-code.js.map +1 -1
  5. package/dist/cli/index.js +195 -2
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/parser/__tests__/parser.test.js +170 -0
  8. package/dist/parser/__tests__/parser.test.js.map +1 -1
  9. package/dist/parser/ast.d.ts +39 -0
  10. package/dist/parser/ast.d.ts.map +1 -1
  11. package/dist/parser/index.d.ts.map +1 -1
  12. package/dist/parser/index.js +58 -2
  13. package/dist/parser/index.js.map +1 -1
  14. package/dist/parser/validator.d.ts.map +1 -1
  15. package/dist/parser/validator.js +66 -3
  16. package/dist/parser/validator.js.map +1 -1
  17. package/dist/visualizer/__tests__/brain.test.d.ts +2 -0
  18. package/dist/visualizer/__tests__/brain.test.d.ts.map +1 -0
  19. package/dist/visualizer/__tests__/brain.test.js +138 -0
  20. package/dist/visualizer/__tests__/brain.test.js.map +1 -0
  21. package/dist/visualizer/__tests__/topology-brain-link.test.d.ts +2 -0
  22. package/dist/visualizer/__tests__/topology-brain-link.test.d.ts.map +1 -0
  23. package/dist/visualizer/__tests__/topology-brain-link.test.js +41 -0
  24. package/dist/visualizer/__tests__/topology-brain-link.test.js.map +1 -0
  25. package/dist/visualizer/brain.d.ts +97 -0
  26. package/dist/visualizer/brain.d.ts.map +1 -0
  27. package/dist/visualizer/brain.js +473 -0
  28. package/dist/visualizer/brain.js.map +1 -0
  29. package/dist/visualizer/index.d.ts +19 -1
  30. package/dist/visualizer/index.d.ts.map +1 -1
  31. package/dist/visualizer/index.js +26 -11
  32. package/dist/visualizer/index.js.map +1 -1
  33. package/examples/company-brain-team.at +241 -0
  34. package/examples/company-brain.at +104 -0
  35. package/examples/logos/gmail.svg +1 -0
  36. package/examples/logos/slack.svg +1 -0
  37. package/package.json +1 -1
  38. package/spec/grammar.md +30 -2
@@ -0,0 +1,138 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { parseBrainVault, renderBrainHtml } from "../brain.js";
6
+ /**
7
+ * Builds a throwaway vault on disk, runs the parser against it, and tears it
8
+ * down. Exercises the real file-walking + link/tag extraction logic.
9
+ */
10
+ describe("Brain visualizer — vault parser", () => {
11
+ let vault;
12
+ beforeAll(() => {
13
+ vault = fs.mkdtempSync(path.join(os.tmpdir(), "brain-test-"));
14
+ fs.writeFileSync(path.join(vault, "_brain.md"), `---\ntags:\n - brain/index\n---\n\n# Brain\n\n- [[acme-corp]]\n- [[deal]]\n`);
15
+ fs.writeFileSync(path.join(vault, "acme-corp.md"), `---\ntags:\n - client/enterprise\n - status/active\n---\n\n# Acme Corp\n\nClosed the [[deal|Q3 deal]] with [[dana]]. On [[enterprise-pricing]].\n`);
16
+ fs.writeFileSync(path.join(vault, "deal.md"), `---\ntags: [deal/closed, client/enterprise]\n---\n\n# Q3 Deal\n\nWith [[acme-corp]]. Tagged #revenue inline.\n`);
17
+ // enterprise-pricing.md and dana.md intentionally NOT created → ghost nodes.
18
+ });
19
+ afterAll(() => {
20
+ fs.rmSync(vault, { recursive: true, force: true });
21
+ });
22
+ it("treats each .md file as a node and links as edges", () => {
23
+ const g = parseBrainVault(vault);
24
+ const realIds = g.nodes.filter((n) => n.kind !== "ghost").map((n) => n.id).sort();
25
+ expect(realIds).toEqual(["_brain", "acme-corp", "deal"]);
26
+ // acme-corp -> deal, acme-corp -> dana, acme-corp -> enterprise-pricing, etc.
27
+ expect(g.edges.some((e) => e.from === "acme-corp" && e.to === "deal")).toBe(true);
28
+ expect(g.edges.some((e) => e.from === "deal" && e.to === "acme-corp")).toBe(true);
29
+ });
30
+ it("detects ghost nodes (linked but no file)", () => {
31
+ const g = parseBrainVault(vault);
32
+ const ghosts = g.nodes.filter((n) => n.kind === "ghost").map((n) => n.id).sort();
33
+ expect(ghosts).toEqual(["dana", "enterprise-pricing"]);
34
+ });
35
+ it("marks index/hub notes (underscore prefix or brain/index tag)", () => {
36
+ const g = parseBrainVault(vault);
37
+ expect(g.nodes.find((n) => n.id === "_brain")?.kind).toBe("index");
38
+ });
39
+ it("parses list-form AND inline-array frontmatter tags, plus inline #tags", () => {
40
+ const g = parseBrainVault(vault);
41
+ const acme = g.nodes.find((n) => n.id === "acme-corp");
42
+ expect(acme?.tags).toContain("client/enterprise"); // list form
43
+ expect(acme?.tags).toContain("status/active");
44
+ const deal = g.nodes.find((n) => n.id === "deal");
45
+ expect(deal?.tags).toContain("deal/closed"); // inline-array form
46
+ expect(deal?.tags).toContain("revenue"); // inline #tag in body
47
+ });
48
+ it("dedupes repeated links within a note (one edge per pair)", () => {
49
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "brain-dup-"));
50
+ fs.writeFileSync(path.join(dir, "a.md"), `# A\n[[b]] and again [[b]] and [[b|once more]].\n`);
51
+ fs.writeFileSync(path.join(dir, "b.md"), `# B\n`);
52
+ const g = parseBrainVault(dir);
53
+ expect(g.edges.filter((e) => e.from === "a" && e.to === "b")).toHaveLength(1);
54
+ fs.rmSync(dir, { recursive: true, force: true });
55
+ });
56
+ it("uses the first # heading as the node title, falls back to the slug", () => {
57
+ const g = parseBrainVault(vault);
58
+ expect(g.nodes.find((n) => n.id === "acme-corp")?.title).toBe("Acme Corp");
59
+ expect(g.nodes.find((n) => n.id === "dana")?.title).toBe("dana"); // ghost → slug
60
+ });
61
+ it("renders a self-contained HTML page with the graph data inlined", () => {
62
+ const g = parseBrainVault(vault);
63
+ const html = renderBrainHtml(g);
64
+ expect(html).toContain("<!doctype html>");
65
+ expect(html).not.toContain("<script src"); // no external scripts — fully self-contained
66
+ expect(html).toContain('"acme-corp"'); // node data inlined
67
+ });
68
+ it("declares interaction state (drag) before the animation loop that reads it", () => {
69
+ // Regression: `let drag` is in a temporal dead zone until its line runs.
70
+ // step() reads `drag` every frame, so if the loop starts before `let drag`,
71
+ // the first frame throws "Cannot access 'drag' before initialization" and
72
+ // the canvas never renders. Guard the declaration order.
73
+ const html = renderBrainHtml(parseBrainVault(vault));
74
+ const dragDecl = html.indexOf("let drag");
75
+ const loopStart = html.indexOf("function loop()");
76
+ expect(dragDecl).toBeGreaterThan(-1);
77
+ expect(loopStart).toBeGreaterThan(-1);
78
+ expect(dragDecl).toBeLessThan(loopStart);
79
+ });
80
+ it("initializes center force at the integrator's scale (not the raw slider value)", () => {
81
+ // Regression: the slider stores value/1000; the initial value must match or
82
+ // the first frames fling every node off-screen before it settles.
83
+ const html = renderBrainHtml(parseBrainVault(vault));
84
+ expect(html).toContain("center:0.008");
85
+ expect(html).not.toMatch(/center:8\b/);
86
+ });
87
+ it("infers ghost CATEGORY from namespaced tags (a person stays a person)", () => {
88
+ // A note tagged person/x, org/y, topic/z tells us the category of the
89
+ // ghost slugs x, y, z even though their notes don't exist. This is what
90
+ // makes Amitai a 'person' node, not a generic 'ghost'.
91
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "brain-cat-"));
92
+ fs.writeFileSync(path.join(dir, "meeting.md"), `---\ntags:\n - person/amitai\n - org/acme\n - topic/litigation-ai\n---\n# Meeting\n[[amitai]] from [[acme]] re [[litigation-ai]].\n`);
93
+ const g = parseBrainVault(dir);
94
+ const byId = Object.fromEntries(g.nodes.map((n) => [n.id, n]));
95
+ expect(byId["amitai"].kind).toBe("ghost");
96
+ expect(byId["amitai"].category).toBe("person"); // not a generic ghost — a person
97
+ expect(byId["acme"].category).toBe("org");
98
+ expect(byId["litigation-ai"].category).toBe("topic");
99
+ fs.rmSync(dir, { recursive: true, force: true });
100
+ });
101
+ it("inlines note content for offline click-to-open (no server needed)", () => {
102
+ const g = parseBrainVault(vault);
103
+ const acme = g.nodes.find((n) => n.id === "acme-corp");
104
+ expect(acme?.content).toContain("Acme Corp"); // body inlined into the node
105
+ const html = renderBrainHtml(g);
106
+ expect(html).toContain("openNode"); // the click-to-open handler is present
107
+ });
108
+ it("emits a topology back-link when given a topologyHref", () => {
109
+ const html = renderBrainHtml(parseBrainVault(vault), {
110
+ topologyHref: "company-brain-team-topology.html",
111
+ topologyName: "company-brain-team",
112
+ });
113
+ expect(html).toContain('class="back-link"');
114
+ expect(html).toContain("company-brain-team-topology.html");
115
+ expect(html).toContain("← company-brain-team");
116
+ });
117
+ it("omits the back-link when no topology owns the vault", () => {
118
+ const html = renderBrainHtml(parseBrainVault(vault));
119
+ expect(html).not.toContain('class="back-link"');
120
+ });
121
+ it("reads a note's `source:` frontmatter as a node property", () => {
122
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "brain-src-"));
123
+ fs.writeFileSync(path.join(dir, "deal.md"), `---\nsource: gmail\ntags:\n - deal/closed\n---\n# Deal\nFrom email.\n`);
124
+ const g = parseBrainVault(dir);
125
+ expect(g.nodes.find((n) => n.id === "deal")?.source).toBe("gmail");
126
+ fs.rmSync(dir, { recursive: true, force: true });
127
+ });
128
+ it("passes the source style map into the rendered graph (presentation only)", () => {
129
+ const g = parseBrainVault(vault);
130
+ const html = renderBrainHtml(g, {
131
+ sources: { gmail: { color: "#EA4335", icon: "data:image/svg+xml;base64,AAA" } },
132
+ });
133
+ expect(html).toContain('"gmail":{"color":"#EA4335"');
134
+ expect(html).toContain("data:image/svg+xml;base64,AAA"); // icon arrives pre-inlined
135
+ expect(html).toContain("DATA.sources"); // the renderer consults the map
136
+ });
137
+ });
138
+ //# sourceMappingURL=brain.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain.test.js","sourceRoot":"","sources":["../../../src/visualizer/__tests__/brain.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=topology-brain-link.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topology-brain-link.test.d.ts","sourceRoot":"","sources":["../../../src/visualizer/__tests__/topology-brain-link.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,41 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parse } from "../../parser/index.js";
3
+ import { generateVisualization } from "../index.js";
4
+ /** A minimal topology with one brain store. */
5
+ const ONE_BRAIN = `topology cb : [brain] {
6
+ meta { version: "1.0.0" description: "x" }
7
+ memory { store brain { type: brain path: "brain/" format: obsidian } }
8
+ agent lib { model: sonnet description: "k" tools: [Read, Write] custodian-of: [brain] }
9
+ action intake { kind: inline description: "in" }
10
+ flow { intake -> lib }
11
+ }`;
12
+ describe("Topology visualizer — brain cross-link", () => {
13
+ // Note: `brain-link` / `brain-menu-list` appear as CSS class definitions in
14
+ // the stylesheet regardless. Assert on the actual RENDERED anchors instead.
15
+ it("renders no brain anchor when no brains are passed", () => {
16
+ const html = generateVisualization(parse(ONE_BRAIN));
17
+ expect(html).not.toContain("Open Brain");
18
+ expect(html).not.toMatch(/\d+ Brains ▾/);
19
+ });
20
+ it("renders a single 'Open Brain →' button for one brain", () => {
21
+ const html = generateVisualization(parse(ONE_BRAIN), {
22
+ brains: [{ id: "brain", href: "cb-brain-brain.html" }],
23
+ });
24
+ expect(html).toContain("Open Brain");
25
+ expect(html).toContain('href="cb-brain-brain.html"');
26
+ expect(html).not.toMatch(/\d+ Brains ▾/); // single → button, not dropdown
27
+ });
28
+ it("renders a dropdown listing each brain when there are several", () => {
29
+ const html = generateVisualization(parse(ONE_BRAIN), {
30
+ brains: [
31
+ { id: "client-brain", href: "cb-brain-client-brain.html" },
32
+ { id: "internal-brain", href: "cb-brain-internal-brain.html" },
33
+ ],
34
+ });
35
+ expect(html).toContain("2 Brains ▾");
36
+ expect(html).toContain('href="cb-brain-client-brain.html"');
37
+ expect(html).toContain('href="cb-brain-internal-brain.html"');
38
+ expect(html).not.toContain("Open Brain"); // dropdown, not single button
39
+ });
40
+ });
41
+ //# sourceMappingURL=topology-brain-link.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topology-brain-link.test.js","sourceRoot":"","sources":["../../../src/visualizer/__tests__/topology-brain-link.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEpD,+CAA+C;AAC/C,MAAM,SAAS,GAAG;;;;;;EAMhB,CAAC;AAEH,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;IACtD,4EAA4E;IAC5E,4EAA4E;IAC5E,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,IAAI,GAAG,qBAAqB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACzC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAC9D,MAAM,IAAI,GAAG,qBAAqB,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE;YACnD,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,qBAAqB,EAAE,CAAC;SACvD,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,gCAAgC;IAC5E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8DAA8D,EAAE,GAAG,EAAE;QACtE,MAAM,IAAI,GAAG,qBAAqB,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE;YACnD,MAAM,EAAE;gBACN,EAAE,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,4BAA4B,EAAE;gBAC1D,EAAE,EAAE,EAAE,gBAAgB,EAAE,IAAI,EAAE,8BAA8B,EAAE;aAC/D;SACF,CAAC,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,mCAAmC,CAAC,CAAC;QAC5D,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,qCAAqC,CAAC,CAAC;QAC9D,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC,8BAA8B;IAC1E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Brain visualizer — renders a folder of Obsidian-format markdown as an
3
+ * interactive force-directed graph, the way Obsidian's graph view does, but as
4
+ * a single self-contained HTML file with zero dependencies. No 500MB Electron
5
+ * app needed to *see* the brain.
6
+ *
7
+ * The graph is a projection of the files (same model as Obsidian):
8
+ * - each `.md` file is a NODE
9
+ * - each `[[wikilink]]` is a directed EDGE
10
+ * - a `[[link]]` to a file that doesn't exist is a GHOST node
11
+ * - `#tags` / frontmatter tags are shown as tag nodes (hyperedges)
12
+ *
13
+ * Layout uses the same four forces Obsidian exposes: center pull, node repel,
14
+ * link spring, and link distance.
15
+ */
16
+ /** A node in the brain graph. */
17
+ export interface BrainNode {
18
+ /** Slug (filename without .md), used as the link target. */
19
+ id: string;
20
+ /** Display title (from first `# heading`, else the slug). */
21
+ title: string;
22
+ /**
23
+ * Existence status — orthogonal to category. "written" = a real .md file,
24
+ * "ghost" = linked but not yet written, "index" = a hub note.
25
+ */
26
+ kind: "written" | "ghost" | "index";
27
+ /**
28
+ * What KIND of thing this is, inferred from tag namespaces (person/org/topic)
29
+ * — NOT the same as existence. A person who has no note yet is category
30
+ * "person", kind "ghost". This is what makes the graph a knowledge graph
31
+ * rather than a bag of links: a person is colored like a person whether or
32
+ * not their note exists.
33
+ */
34
+ category: "person" | "org" | "topic" | "note";
35
+ /** Frontmatter + inline tags on this note. */
36
+ tags: string[];
37
+ /** Number of inbound links (for sizing). */
38
+ inbound: number;
39
+ /** Raw markdown body (written notes only) — inlined so click-to-open works offline. */
40
+ content?: string;
41
+ /**
42
+ * Provenance — the `source:` frontmatter value (e.g. "gmail", "slack"),
43
+ * stamped by the ingester that wrote the note. Drives source-coloring in the
44
+ * graph when the topology declares a `sources { ... }` style map.
45
+ */
46
+ source?: string;
47
+ }
48
+ /** A directed edge from one note to another (a `[[wikilink]]`). */
49
+ export interface BrainEdge {
50
+ from: string;
51
+ to: string;
52
+ }
53
+ /** The parsed brain graph. */
54
+ export interface BrainGraph {
55
+ nodes: BrainNode[];
56
+ edges: BrainEdge[];
57
+ /** Distinct tags across the vault, with how many notes carry each. */
58
+ tags: {
59
+ tag: string;
60
+ count: number;
61
+ }[];
62
+ vaultPath: string;
63
+ }
64
+ /**
65
+ * Parse a folder of Obsidian-format markdown into a brain graph.
66
+ *
67
+ * @param vaultPath - Directory containing the `.md` notes.
68
+ */
69
+ export declare function parseBrainVault(vaultPath: string): BrainGraph;
70
+ /** Options for rendering the brain HTML. */
71
+ export interface RenderBrainOptions {
72
+ /**
73
+ * Relative href to the topology visualization for this brain, if known.
74
+ * When set, the brain view shows a "← Topology" link back to it.
75
+ */
76
+ topologyHref?: string;
77
+ /** Display name of the owning topology (shown on the back-link). */
78
+ topologyName?: string;
79
+ /**
80
+ * Provenance styling map: source key (e.g. "gmail") → { color, icon }, where
81
+ * `icon` is ALREADY a data: URI (the CLI inlines the file before calling this,
82
+ * so the renderer stays pure and the output stays self-contained). When a
83
+ * node's `source` matches a key, it is colored/iconed accordingly; otherwise
84
+ * it falls back to category coloring. Presentation only.
85
+ */
86
+ sources?: Record<string, {
87
+ color?: string;
88
+ icon?: string;
89
+ }>;
90
+ }
91
+ /**
92
+ * Render a brain graph as a self-contained, dependency-free HTML page with an
93
+ * Obsidian-style force-directed graph view, styled to match the AgentTopology
94
+ * visualizer.
95
+ */
96
+ export declare function renderBrainHtml(graph: BrainGraph, opts?: RenderBrainOptions): string;
97
+ //# sourceMappingURL=brain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brain.d.ts","sourceRoot":"","sources":["../../src/visualizer/brain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,iCAAiC;AACjC,MAAM,WAAW,SAAS;IACxB,4DAA4D;IAC5D,EAAE,EAAE,MAAM,CAAC;IACX,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;IACpC;;;;;;OAMG;IACH,QAAQ,EAAE,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,CAAC;IAC9C,8CAA8C;IAC9C,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAaD,mEAAmE;AACnE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,8BAA8B;AAC9B,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,sEAAsE;IACtE,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC;CACnB;AA4ED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAuF7D;AAED,4CAA4C;AAC5C,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,GAAE,kBAAuB,GAAG,MAAM,CAwBxF"}