agentopology 0.2.5 → 0.4.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.
- package/README.md +121 -7
- package/dist/analyzer/__tests__/analyzer.test.js +36 -9
- package/dist/analyzer/__tests__/analyzer.test.js.map +1 -1
- package/dist/analyzer/__tests__/observability-gap.test.js +40 -10
- package/dist/analyzer/__tests__/observability-gap.test.js.map +1 -1
- package/dist/analyzer/index.d.ts +2 -1
- package/dist/analyzer/index.d.ts.map +1 -1
- package/dist/analyzer/index.js +1 -1
- package/dist/analyzer/index.js.map +1 -1
- package/dist/bindings/__tests__/bindings.test.js +20 -5
- package/dist/bindings/__tests__/bindings.test.js.map +1 -1
- package/dist/bindings/__tests__/claude-workflow.test.js +12 -3
- package/dist/bindings/__tests__/claude-workflow.test.js.map +1 -1
- package/dist/bindings/__tests__/defaults-agreement.test.d.ts +16 -0
- package/dist/bindings/__tests__/defaults-agreement.test.d.ts.map +1 -0
- package/dist/bindings/__tests__/defaults-agreement.test.js +80 -0
- package/dist/bindings/__tests__/defaults-agreement.test.js.map +1 -0
- package/dist/bindings/__tests__/observability.test.js +8 -2
- package/dist/bindings/__tests__/observability.test.js.map +1 -1
- package/dist/bindings/__tests__/stub-generation.test.js +8 -2
- package/dist/bindings/__tests__/stub-generation.test.js.map +1 -1
- package/dist/bindings/claude-code.d.ts.map +1 -1
- package/dist/bindings/claude-code.js +102 -3
- package/dist/bindings/claude-code.js.map +1 -1
- package/dist/bindings/claude-workflow.d.ts.map +1 -1
- package/dist/bindings/claude-workflow.js +2 -1
- package/dist/bindings/claude-workflow.js.map +1 -1
- package/dist/bindings/codex.d.ts.map +1 -1
- package/dist/bindings/codex.js +6 -2
- package/dist/bindings/codex.js.map +1 -1
- package/dist/bindings/gemini-cli.d.ts.map +1 -1
- package/dist/bindings/gemini-cli.js +2 -1
- package/dist/bindings/gemini-cli.js.map +1 -1
- package/dist/bindings/kiro.d.ts.map +1 -1
- package/dist/bindings/kiro.js +2 -1
- package/dist/bindings/kiro.js.map +1 -1
- package/dist/bindings/openclaw.d.ts.map +1 -1
- package/dist/bindings/openclaw.js +2 -1
- package/dist/bindings/openclaw.js.map +1 -1
- package/dist/cli/__tests__/cli.test.d.ts +12 -0
- package/dist/cli/__tests__/cli.test.d.ts.map +1 -0
- package/dist/cli/__tests__/cli.test.js +171 -0
- package/dist/cli/__tests__/cli.test.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +344 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/docs/content.d.ts.map +1 -1
- package/dist/docs/content.js +112 -0
- package/dist/docs/content.js.map +1 -1
- package/dist/exporters/ascii.d.ts +13 -0
- package/dist/exporters/ascii.d.ts.map +1 -0
- package/dist/exporters/ascii.js +26 -0
- package/dist/exporters/ascii.js.map +1 -0
- package/dist/exporters/brief.d.ts +14 -0
- package/dist/exporters/brief.d.ts.map +1 -0
- package/dist/exporters/brief.js +37 -0
- package/dist/exporters/brief.js.map +1 -0
- package/dist/exporters/index.d.ts +2 -0
- package/dist/exporters/index.d.ts.map +1 -1
- package/dist/exporters/index.js +6 -0
- package/dist/exporters/index.js.map +1 -1
- package/dist/exporters/markdown.d.ts.map +1 -1
- package/dist/exporters/markdown.js +2 -1
- package/dist/exporters/markdown.js.map +1 -1
- package/dist/exporters/mermaid.d.ts.map +1 -1
- package/dist/exporters/mermaid.js +2 -1
- package/dist/exporters/mermaid.js.map +1 -1
- package/dist/import/__tests__/import.test.js +8 -2
- package/dist/import/__tests__/import.test.js.map +1 -1
- package/dist/parser/__tests__/parser.test.js +1553 -207
- package/dist/parser/__tests__/parser.test.js.map +1 -1
- package/dist/parser/__tests__/spec-sync.test.d.ts +10 -0
- package/dist/parser/__tests__/spec-sync.test.d.ts.map +1 -0
- package/dist/parser/__tests__/spec-sync.test.js +60 -0
- package/dist/parser/__tests__/spec-sync.test.js.map +1 -0
- package/dist/parser/ast.d.ts +39 -0
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/index.d.ts +50 -6
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +241 -30
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/validator.d.ts +7 -1
- package/dist/parser/validator.d.ts.map +1 -1
- package/dist/parser/validator.js +143 -5
- package/dist/parser/validator.js.map +1 -1
- package/dist/plan/__tests__/brief.test.d.ts +2 -0
- package/dist/plan/__tests__/brief.test.d.ts.map +1 -0
- package/dist/plan/__tests__/brief.test.js +442 -0
- package/dist/plan/__tests__/brief.test.js.map +1 -0
- package/dist/plan/brief.d.ts +207 -0
- package/dist/plan/brief.d.ts.map +1 -0
- package/dist/plan/brief.js +342 -0
- package/dist/plan/brief.js.map +1 -0
- package/dist/plan/render.d.ts +20 -0
- package/dist/plan/render.d.ts.map +1 -0
- package/dist/plan/render.js +425 -0
- package/dist/plan/render.js.map +1 -0
- package/dist/render/__tests__/ascii.test.d.ts +2 -0
- package/dist/render/__tests__/ascii.test.d.ts.map +1 -0
- package/dist/render/__tests__/ascii.test.js +171 -0
- package/dist/render/__tests__/ascii.test.js.map +1 -0
- package/dist/render/ascii.d.ts +23 -0
- package/dist/render/ascii.d.ts.map +1 -0
- package/dist/render/ascii.js +178 -0
- package/dist/render/ascii.js.map +1 -0
- package/dist/resolve/__tests__/defaults.test.d.ts +2 -0
- package/dist/resolve/__tests__/defaults.test.d.ts.map +1 -0
- package/dist/resolve/__tests__/defaults.test.js +109 -0
- package/dist/resolve/__tests__/defaults.test.js.map +1 -0
- package/dist/resolve/__tests__/order.test.d.ts +2 -0
- package/dist/resolve/__tests__/order.test.d.ts.map +1 -0
- package/dist/resolve/__tests__/order.test.js +238 -0
- package/dist/resolve/__tests__/order.test.js.map +1 -0
- package/dist/resolve/__tests__/phase.test.d.ts +12 -0
- package/dist/resolve/__tests__/phase.test.d.ts.map +1 -0
- package/dist/resolve/__tests__/phase.test.js +81 -0
- package/dist/resolve/__tests__/phase.test.js.map +1 -0
- package/dist/resolve/defaults.d.ts +51 -0
- package/dist/resolve/defaults.d.ts.map +1 -0
- package/dist/resolve/defaults.js +109 -0
- package/dist/resolve/defaults.js.map +1 -0
- package/dist/resolve/order.d.ts +73 -0
- package/dist/resolve/order.d.ts.map +1 -0
- package/dist/resolve/order.js +208 -0
- package/dist/resolve/order.js.map +1 -0
- package/dist/resolve/phase.d.ts +57 -0
- package/dist/resolve/phase.d.ts.map +1 -0
- package/dist/resolve/phase.js +60 -0
- package/dist/resolve/phase.js.map +1 -0
- package/dist/scaffold/__tests__/scaffold.test.js +8 -2
- package/dist/scaffold/__tests__/scaffold.test.js.map +1 -1
- package/dist/scaffold/__tests__/settings-merge.test.js +95 -0
- package/dist/scaffold/__tests__/settings-merge.test.js.map +1 -1
- package/dist/scaffold/incremental.d.ts.map +1 -1
- package/dist/scaffold/incremental.js +13 -2
- package/dist/scaffold/incremental.js.map +1 -1
- package/dist/visualizer/__tests__/brain.test.d.ts +2 -0
- package/dist/visualizer/__tests__/brain.test.d.ts.map +1 -0
- package/dist/visualizer/__tests__/brain.test.js +138 -0
- package/dist/visualizer/__tests__/brain.test.js.map +1 -0
- package/dist/visualizer/__tests__/topology-brain-link.test.d.ts +2 -0
- package/dist/visualizer/__tests__/topology-brain-link.test.d.ts.map +1 -0
- package/dist/visualizer/__tests__/topology-brain-link.test.js +56 -0
- package/dist/visualizer/__tests__/topology-brain-link.test.js.map +1 -0
- package/dist/visualizer/brain.d.ts +97 -0
- package/dist/visualizer/brain.d.ts.map +1 -0
- package/dist/visualizer/brain.js +473 -0
- package/dist/visualizer/brain.js.map +1 -0
- package/dist/visualizer/index.d.ts +19 -1
- package/dist/visualizer/index.d.ts.map +1 -1
- package/dist/visualizer/index.js +26 -11
- package/dist/visualizer/index.js.map +1 -1
- package/examples/code-review.at +32 -4
- package/examples/company-brain-team.at +241 -0
- package/examples/company-brain.at +104 -0
- package/examples/data-processing.at +33 -4
- package/examples/logos/gmail.svg +1 -0
- package/examples/logos/slack.svg +1 -0
- package/package.json +1 -1
- package/spec/grammar.md +55 -3
- package/spec/validation.md +103 -35
|
@@ -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"}
|
|
@@ -0,0 +1,473 @@
|
|
|
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
|
+
import * as fs from "node:fs";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
/** Map a tag namespace prefix to a node category. */
|
|
19
|
+
function categoryFromTags(tags) {
|
|
20
|
+
for (const t of tags) {
|
|
21
|
+
const ns = t.split("/")[0];
|
|
22
|
+
if (ns === "person" || ns === "people")
|
|
23
|
+
return "person";
|
|
24
|
+
if (ns === "org" || ns === "client" || ns === "company")
|
|
25
|
+
return "org";
|
|
26
|
+
if (ns === "topic" || ns === "tag")
|
|
27
|
+
return "topic";
|
|
28
|
+
}
|
|
29
|
+
return "note";
|
|
30
|
+
}
|
|
31
|
+
const WIKILINK_RE = /\[\[([^\]]+)\]\]/g;
|
|
32
|
+
const INLINE_TAG_RE = /(?:^|\s)#([a-z0-9][a-z0-9_-]*(?:\/[a-z0-9][a-z0-9_-]*)*)/gi;
|
|
33
|
+
/** Normalize a wikilink target: strip alias (`|...`), heading (`#...`), block (`^...`). */
|
|
34
|
+
function linkTarget(raw) {
|
|
35
|
+
let t = raw.split("|")[0];
|
|
36
|
+
t = t.split("#")[0];
|
|
37
|
+
t = t.split("^")[0];
|
|
38
|
+
return t.trim().replace(/\.md$/i, "");
|
|
39
|
+
}
|
|
40
|
+
/** Extract `tags:` from simple YAML frontmatter (list or inline array form). */
|
|
41
|
+
function frontmatterTags(content) {
|
|
42
|
+
const fm = /^---\n([\s\S]*?)\n---/.exec(content);
|
|
43
|
+
if (!fm)
|
|
44
|
+
return [];
|
|
45
|
+
const body = fm[1];
|
|
46
|
+
const tags = [];
|
|
47
|
+
// Inline array: `tags: [a, b, c]`
|
|
48
|
+
const inline = /^tags:\s*\[([^\]]*)\]/m.exec(body);
|
|
49
|
+
if (inline) {
|
|
50
|
+
for (const t of inline[1].split(",")) {
|
|
51
|
+
const v = t.trim().replace(/^["']|["']$/g, "");
|
|
52
|
+
if (v)
|
|
53
|
+
tags.push(v);
|
|
54
|
+
}
|
|
55
|
+
return tags;
|
|
56
|
+
}
|
|
57
|
+
// List form:
|
|
58
|
+
// tags:
|
|
59
|
+
// - a
|
|
60
|
+
// - b
|
|
61
|
+
const listHeader = /^tags:\s*$/m.exec(body);
|
|
62
|
+
if (listHeader) {
|
|
63
|
+
const after = body.slice(listHeader.index + listHeader[0].length);
|
|
64
|
+
for (const line of after.split("\n")) {
|
|
65
|
+
if (line.trim() === "")
|
|
66
|
+
continue; // skip the blank line right after `tags:`
|
|
67
|
+
const m = /^\s*-\s*(.+)$/.exec(line);
|
|
68
|
+
if (!m)
|
|
69
|
+
break; // list ends at first non-item, non-blank line
|
|
70
|
+
const v = m[1].trim().replace(/^["']|["']$/g, "");
|
|
71
|
+
if (v)
|
|
72
|
+
tags.push(v);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return tags;
|
|
76
|
+
}
|
|
77
|
+
/** First `# Heading` in the body, else null. */
|
|
78
|
+
/** Read a single scalar field (e.g. `source:`) from YAML frontmatter. */
|
|
79
|
+
function frontmatterField(content, key) {
|
|
80
|
+
const fm = /^---\n([\s\S]*?)\n---/.exec(content);
|
|
81
|
+
if (!fm)
|
|
82
|
+
return undefined;
|
|
83
|
+
const m = new RegExp(`^${key}:\\s*(.+)$`, "m").exec(fm[1]);
|
|
84
|
+
if (!m)
|
|
85
|
+
return undefined;
|
|
86
|
+
const v = m[1].trim().replace(/^["']|["']$/g, "");
|
|
87
|
+
return v || undefined;
|
|
88
|
+
}
|
|
89
|
+
function firstHeading(content) {
|
|
90
|
+
const m = /^#\s+(.+)$/m.exec(content.replace(/^---\n[\s\S]*?\n---\n?/, ""));
|
|
91
|
+
return m ? m[1].trim() : null;
|
|
92
|
+
}
|
|
93
|
+
/** Recursively collect `.md` files under a directory. */
|
|
94
|
+
function walkMarkdown(dir) {
|
|
95
|
+
const out = [];
|
|
96
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
97
|
+
if (entry.name.startsWith("."))
|
|
98
|
+
continue;
|
|
99
|
+
const full = path.join(dir, entry.name);
|
|
100
|
+
if (entry.isDirectory())
|
|
101
|
+
out.push(...walkMarkdown(full));
|
|
102
|
+
else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md"))
|
|
103
|
+
out.push(full);
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Parse a folder of Obsidian-format markdown into a brain graph.
|
|
109
|
+
*
|
|
110
|
+
* @param vaultPath - Directory containing the `.md` notes.
|
|
111
|
+
*/
|
|
112
|
+
export function parseBrainVault(vaultPath) {
|
|
113
|
+
const files = walkMarkdown(vaultPath);
|
|
114
|
+
const realIds = new Set(files.map((f) => path.basename(f, path.extname(f))));
|
|
115
|
+
const nodes = new Map();
|
|
116
|
+
const edges = [];
|
|
117
|
+
const tagCounts = new Map();
|
|
118
|
+
// slug -> inferred category, learned from `person/x` / `org/x` / `topic/x` tags.
|
|
119
|
+
const ghostCategory = new Map();
|
|
120
|
+
for (const file of files) {
|
|
121
|
+
const id = path.basename(file, ".md");
|
|
122
|
+
const content = fs.readFileSync(file, "utf-8");
|
|
123
|
+
const fmTags = frontmatterTags(content);
|
|
124
|
+
const inlineTags = [];
|
|
125
|
+
let m;
|
|
126
|
+
INLINE_TAG_RE.lastIndex = 0;
|
|
127
|
+
while ((m = INLINE_TAG_RE.exec(content)) !== null)
|
|
128
|
+
inlineTags.push(m[1]);
|
|
129
|
+
const tags = Array.from(new Set([...fmTags, ...inlineTags]));
|
|
130
|
+
for (const t of tags)
|
|
131
|
+
tagCounts.set(t, (tagCounts.get(t) ?? 0) + 1);
|
|
132
|
+
// Index notes are hubs: filename starting with `_` or tagged brain/index.
|
|
133
|
+
const isIndex = id.startsWith("_") || tags.some((t) => t === "brain/index");
|
|
134
|
+
nodes.set(id, {
|
|
135
|
+
id,
|
|
136
|
+
title: firstHeading(content) ?? id,
|
|
137
|
+
kind: isIndex ? "index" : "written",
|
|
138
|
+
category: categoryFromTags(tags),
|
|
139
|
+
tags,
|
|
140
|
+
inbound: 0,
|
|
141
|
+
content,
|
|
142
|
+
source: frontmatterField(content, "source"),
|
|
143
|
+
});
|
|
144
|
+
// A tag like `person/amitai-eliram` tells us the SLUG `amitai-eliram` is a
|
|
145
|
+
// person — even if its note doesn't exist yet. Record that so ghosts get the
|
|
146
|
+
// right category (a person stays a person whether or not their note exists).
|
|
147
|
+
for (const t of tags) {
|
|
148
|
+
const [ns, slug] = t.split("/");
|
|
149
|
+
if (!slug)
|
|
150
|
+
continue;
|
|
151
|
+
const cat = ns === "person" || ns === "people" ? "person" :
|
|
152
|
+
ns === "org" || ns === "client" || ns === "company" ? "org" :
|
|
153
|
+
ns === "topic" ? "topic" : null;
|
|
154
|
+
if (cat && !ghostCategory.has(slug))
|
|
155
|
+
ghostCategory.set(slug, cat);
|
|
156
|
+
}
|
|
157
|
+
// Extract wikilink edges — dedupe within a note (Obsidian's graph draws one
|
|
158
|
+
// edge per (source, target) pair, however many times the link is repeated).
|
|
159
|
+
const seenTargets = new Set();
|
|
160
|
+
WIKILINK_RE.lastIndex = 0;
|
|
161
|
+
while ((m = WIKILINK_RE.exec(content)) !== null) {
|
|
162
|
+
const target = linkTarget(m[1]);
|
|
163
|
+
if (!target || target === id || seenTargets.has(target))
|
|
164
|
+
continue;
|
|
165
|
+
seenTargets.add(target);
|
|
166
|
+
edges.push({ from: id, to: target });
|
|
167
|
+
// Ghost node: linked but no file exists.
|
|
168
|
+
if (!realIds.has(target) && !nodes.has(target)) {
|
|
169
|
+
nodes.set(target, {
|
|
170
|
+
id: target, title: target, kind: "ghost",
|
|
171
|
+
category: "note", tags: [], inbound: 0,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Apply inferred categories to ghost nodes (and any node still "note" that we
|
|
177
|
+
// learned about from another note's namespaced tags).
|
|
178
|
+
for (const n of nodes.values()) {
|
|
179
|
+
if (n.category === "note" && ghostCategory.has(n.id)) {
|
|
180
|
+
n.category = ghostCategory.get(n.id);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Compute inbound counts for sizing.
|
|
184
|
+
for (const e of edges) {
|
|
185
|
+
const n = nodes.get(e.to);
|
|
186
|
+
if (n)
|
|
187
|
+
n.inbound++;
|
|
188
|
+
}
|
|
189
|
+
const tags = Array.from(tagCounts.entries())
|
|
190
|
+
.map(([tag, count]) => ({ tag, count }))
|
|
191
|
+
.sort((a, b) => b.count - a.count);
|
|
192
|
+
return { nodes: Array.from(nodes.values()), edges, tags, vaultPath };
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Render a brain graph as a self-contained, dependency-free HTML page with an
|
|
196
|
+
* Obsidian-style force-directed graph view, styled to match the AgentTopology
|
|
197
|
+
* visualizer.
|
|
198
|
+
*/
|
|
199
|
+
export function renderBrainHtml(graph, opts = {}) {
|
|
200
|
+
const vaultName = path.basename(graph.vaultPath.replace(/\/+$/, "")) || "brain";
|
|
201
|
+
const realCount = graph.nodes.filter((n) => n.kind !== "ghost").length;
|
|
202
|
+
const ghostCount = graph.nodes.filter((n) => n.kind === "ghost").length;
|
|
203
|
+
const data = JSON.stringify({
|
|
204
|
+
nodes: graph.nodes,
|
|
205
|
+
edges: graph.edges,
|
|
206
|
+
tags: graph.tags.slice(0, 24),
|
|
207
|
+
sources: opts.sources ?? {},
|
|
208
|
+
});
|
|
209
|
+
const backLink = opts.topologyHref
|
|
210
|
+
? `<a class="back-link" href="${escapeHtml(opts.topologyHref)}">← ${escapeHtml(opts.topologyName ?? "Topology")}</a>`
|
|
211
|
+
: "";
|
|
212
|
+
return BRAIN_HTML
|
|
213
|
+
.replace(/__VAULT__/g, escapeHtml(vaultName))
|
|
214
|
+
.replace("__NOTES__", String(realCount))
|
|
215
|
+
.replace("__LINKS__", String(graph.edges.length))
|
|
216
|
+
.replace("__GHOSTS__", String(ghostCount))
|
|
217
|
+
.replace("__TAGS__", String(graph.tags.length))
|
|
218
|
+
.replace("__BACKLINK__", backLink)
|
|
219
|
+
.replace("__DATA__", data);
|
|
220
|
+
}
|
|
221
|
+
function escapeHtml(s) {
|
|
222
|
+
return s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The HTML shell. Self-contained: inline CSS + a tiny force-directed canvas
|
|
226
|
+
* renderer using the same four forces Obsidian exposes (center, repel, link
|
|
227
|
+
* spring, link distance). Styled to match the AgentTopology visualizer.
|
|
228
|
+
* Nodes are colored by CATEGORY (person/org/topic/note); ghosts are drawn
|
|
229
|
+
* hollow (same color, dashed, unfilled) — "not written yet" is a status, not
|
|
230
|
+
* a category. Click a node to open its note in the side panel.
|
|
231
|
+
*/
|
|
232
|
+
const BRAIN_HTML = String.raw `<!doctype html>
|
|
233
|
+
<html lang="en"><head>
|
|
234
|
+
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
235
|
+
<title>__VAULT__ — Brain Graph</title>
|
|
236
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
237
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
238
|
+
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
239
|
+
<style>
|
|
240
|
+
:root{
|
|
241
|
+
--bg:#0A0A0A;--s:#111111;--s2:#161616;--b:#1e1e1e;--b2:#2a2a2a;
|
|
242
|
+
--t:#e4e4ef;--t2:#878593;--t3:#56545e;
|
|
243
|
+
--person:#60a5fa;--org:#4ade80;--topic:#fbbf24;--note:#a78bfa;--index:#22d3ee;
|
|
244
|
+
--accent:#a78bfa;
|
|
245
|
+
--font-mono:'JetBrains Mono',monospace;--font-body:'Noto Sans',sans-serif;
|
|
246
|
+
}
|
|
247
|
+
*{box-sizing:border-box}
|
|
248
|
+
html,body{margin:0;height:100%;background:var(--bg);color:var(--t);
|
|
249
|
+
font-family:var(--font-body);font-size:13px;line-height:1.5;overflow:hidden}
|
|
250
|
+
#app{display:flex;height:100vh}
|
|
251
|
+
#stage{flex:1;position:relative;overflow:hidden}
|
|
252
|
+
#header{position:fixed;top:0;left:0;right:0;height:46px;z-index:15;display:flex;align-items:center;gap:14px;
|
|
253
|
+
padding:0 18px;background:var(--s);border-bottom:1px solid var(--b)}
|
|
254
|
+
#header .brand{display:flex;align-items:center;gap:8px;font-weight:600;font-size:14px}
|
|
255
|
+
#header .vault{font-family:var(--font-mono);font-size:12px;color:var(--t2);background:var(--s2);
|
|
256
|
+
padding:3px 10px;border-radius:6px;border:1px solid var(--b)}
|
|
257
|
+
#header .spacer{flex:1}
|
|
258
|
+
#header .stats{display:flex;gap:14px;font-family:var(--font-mono);font-size:11px;color:var(--t3)}
|
|
259
|
+
#header .stats b{color:var(--t2);font-weight:500}
|
|
260
|
+
.back-link{font-family:var(--font-mono);font-size:11px;color:var(--accent);text-decoration:none;
|
|
261
|
+
border:1px solid var(--b2);border-radius:6px;padding:4px 10px;transition:.15s}
|
|
262
|
+
.back-link:hover{background:rgba(167,139,250,.1);border-color:var(--accent)}
|
|
263
|
+
#search input{background:var(--s2);border:1px solid var(--b);color:var(--t);border-radius:7px;
|
|
264
|
+
padding:6px 11px;width:180px;font-size:12px;font-family:var(--font-body);outline:none}
|
|
265
|
+
#search input:focus{border-color:var(--accent)}
|
|
266
|
+
#legend{position:fixed;left:14px;bottom:14px;z-index:12;background:var(--s);border:1px solid var(--b);
|
|
267
|
+
border-radius:10px;padding:10px 14px}
|
|
268
|
+
#legend .row{display:flex;align-items:center;gap:7px;font-size:11px;color:var(--t2);padding:2px 0}
|
|
269
|
+
#legend .dot{width:9px;height:9px;border-radius:50%;flex:none}
|
|
270
|
+
#legend .hr{height:1px;background:var(--b);margin:7px 0}
|
|
271
|
+
#legend .hollow{font-size:10px;color:var(--t3)}
|
|
272
|
+
#controls{position:fixed;right:14px;bottom:14px;z-index:12;background:var(--s);border:1px solid var(--b);
|
|
273
|
+
border-radius:10px;padding:10px 14px;display:flex;gap:16px;font-size:10px;font-family:var(--font-mono)}
|
|
274
|
+
#controls label{display:flex;flex-direction:column;gap:4px;color:var(--t3);text-transform:uppercase;letter-spacing:.5px}
|
|
275
|
+
#controls input[type=range]{width:78px;accent-color:var(--accent)}
|
|
276
|
+
#tip{position:fixed;z-index:20;background:var(--s);border:1px solid var(--b2);border-radius:8px;
|
|
277
|
+
padding:7px 11px;font-size:11px;pointer-events:none;opacity:0;transition:opacity .12s;max-width:260px}
|
|
278
|
+
#tip.show{opacity:1}
|
|
279
|
+
#tip .t{color:var(--t);font-weight:600}
|
|
280
|
+
#tip .cat{font-family:var(--font-mono);font-size:9px;text-transform:uppercase;letter-spacing:1px}
|
|
281
|
+
#tip .tags{color:var(--topic);font-size:10px;margin-top:3px;font-family:var(--font-mono)}
|
|
282
|
+
canvas{display:block;cursor:grab}
|
|
283
|
+
#panel{width:0;background:var(--s);border-left:1px solid var(--b);overflow:hidden;transition:width .2s;flex:none}
|
|
284
|
+
#panel.open{width:400px}
|
|
285
|
+
#panel-in{width:400px;height:100vh;padding:54px 22px 22px;overflow-y:auto;position:relative}
|
|
286
|
+
.p-head{display:flex;align-items:center;gap:10px;margin-bottom:6px}
|
|
287
|
+
.p-dot{width:13px;height:13px;border-radius:50%;flex:none}
|
|
288
|
+
.p-title{font-size:17px;font-weight:600}
|
|
289
|
+
.p-cat{font-family:var(--font-mono);font-size:9px;text-transform:uppercase;letter-spacing:1px;padding:2px 7px;border-radius:4px}
|
|
290
|
+
.p-ghost{font-family:var(--font-mono);font-size:11px;color:var(--topic);margin:8px 0 12px}
|
|
291
|
+
.p-tags{display:flex;flex-wrap:wrap;gap:5px;margin:10px 0 16px}
|
|
292
|
+
.p-tag{font-family:var(--font-mono);font-size:10px;color:var(--t2);background:var(--s2);border:1px solid var(--b);border-radius:4px;padding:2px 7px}
|
|
293
|
+
.p-md{font-size:13px;line-height:1.7;color:var(--t)}
|
|
294
|
+
.p-md h1,.p-md h2,.p-md h3{font-size:15px;margin:16px 0 6px}
|
|
295
|
+
.p-md a{color:var(--accent);text-decoration:none;cursor:pointer}
|
|
296
|
+
.p-md a:hover{text-decoration:underline}
|
|
297
|
+
.p-md code{font-family:var(--font-mono);font-size:11px;background:var(--s2);padding:1px 5px;border-radius:3px}
|
|
298
|
+
.p-md blockquote{border-left:2px solid var(--b2);margin:8px 0;padding:2px 0 2px 12px;color:var(--t2)}
|
|
299
|
+
.p-md hr{border:none;border-top:1px solid var(--b);margin:14px 0}
|
|
300
|
+
.p-close{position:absolute;top:54px;right:18px;cursor:pointer;color:var(--t3);font-size:18px}
|
|
301
|
+
.p-close:hover{color:var(--t)}
|
|
302
|
+
.p-links{margin-top:18px;border-top:1px solid var(--b);padding-top:12px}
|
|
303
|
+
.p-links h4{font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:1px;color:var(--t3);margin:0 0 8px}
|
|
304
|
+
.p-links a{display:block;color:var(--accent);text-decoration:none;font-size:12px;padding:2px 0;cursor:pointer}
|
|
305
|
+
.p-links a:hover{text-decoration:underline}
|
|
306
|
+
</style></head>
|
|
307
|
+
<body>
|
|
308
|
+
<div id="header">
|
|
309
|
+
<div class="brand">🧠 <span>Brain</span></div>
|
|
310
|
+
<div class="vault">__VAULT__</div>
|
|
311
|
+
__BACKLINK__
|
|
312
|
+
<div class="spacer"></div>
|
|
313
|
+
<div class="stats">
|
|
314
|
+
<span><b>__NOTES__</b> notes</span><span><b>__LINKS__</b> links</span>
|
|
315
|
+
<span><b>__GHOSTS__</b> ghosts</span><span><b>__TAGS__</b> tags</span>
|
|
316
|
+
</div>
|
|
317
|
+
<div id="search"><input id="q" placeholder="Search…" autocomplete="off"></div>
|
|
318
|
+
</div>
|
|
319
|
+
<div id="app">
|
|
320
|
+
<div id="stage">
|
|
321
|
+
<canvas id="c"></canvas>
|
|
322
|
+
<div id="legend">
|
|
323
|
+
<div class="row"><span class="dot" style="background:var(--person)"></span>person</div>
|
|
324
|
+
<div class="row"><span class="dot" style="background:var(--org)"></span>org</div>
|
|
325
|
+
<div class="row"><span class="dot" style="background:var(--topic)"></span>topic</div>
|
|
326
|
+
<div class="row"><span class="dot" style="background:var(--note)"></span>note</div>
|
|
327
|
+
<div class="row"><span class="dot" style="background:var(--index)"></span>index / hub</div>
|
|
328
|
+
<div class="hr"></div>
|
|
329
|
+
<div class="row hollow"><span class="dot" style="background:transparent;border:1.5px dashed var(--t3)"></span>hollow = not written yet</div>
|
|
330
|
+
</div>
|
|
331
|
+
<div id="controls">
|
|
332
|
+
<label>Repel<input type="range" id="repel" min="200" max="6000" value="2200"></label>
|
|
333
|
+
<label>Dist<input type="range" id="dist" min="40" max="300" value="120"></label>
|
|
334
|
+
<label>Center<input type="range" id="center" min="0" max="30" value="8"></label>
|
|
335
|
+
</div>
|
|
336
|
+
<div id="tip"></div>
|
|
337
|
+
</div>
|
|
338
|
+
<div id="panel"><div id="panel-in"></div></div>
|
|
339
|
+
</div>
|
|
340
|
+
<script>
|
|
341
|
+
const DATA = __DATA__;
|
|
342
|
+
const c = document.getElementById('c'), x = c.getContext('2d');
|
|
343
|
+
let W, H, DPR = Math.min(devicePixelRatio||1, 2);
|
|
344
|
+
function size(){ const st=document.getElementById('stage').getBoundingClientRect();
|
|
345
|
+
W=st.width; H=st.height; c.width=W*DPR; c.height=H*DPR; c.style.width=W+'px'; c.style.height=H+'px'; x.setTransform(DPR,0,0,DPR,0,0); }
|
|
346
|
+
size(); addEventListener('resize', size);
|
|
347
|
+
|
|
348
|
+
const CAT = { person:'#60a5fa', org:'#4ade80', topic:'#fbbf24', note:'#a78bfa' };
|
|
349
|
+
const SRC = DATA.sources || {};
|
|
350
|
+
// Color priority: declared source color (provenance) > category > default.
|
|
351
|
+
// Index/hub nodes always use the index color.
|
|
352
|
+
function nodeColor(n){
|
|
353
|
+
if (n.kind==='index') return '#22d3ee';
|
|
354
|
+
if (n.source && SRC[n.source] && SRC[n.source].color) return SRC[n.source].color;
|
|
355
|
+
return CAT[n.category]||CAT.note;
|
|
356
|
+
}
|
|
357
|
+
function nodeIcon(n){ return n.source && SRC[n.source] && SRC[n.source].icon ? SRC[n.source].icon : null; }
|
|
358
|
+
// Lazy image cache for source icons (data: URIs). Decoded once, reused.
|
|
359
|
+
const ICONS = {};
|
|
360
|
+
function getIcon(src){ if(!ICONS[src]){ const im=new Image(); im.src=src; ICONS[src]=im; } return ICONS[src]; }
|
|
361
|
+
|
|
362
|
+
const N = new Map();
|
|
363
|
+
for (const n of DATA.nodes){
|
|
364
|
+
N.set(n.id, Object.assign({}, n, { x: W/2+(Math.random()-.5)*Math.min(W,600), y: H/2+(Math.random()-.5)*Math.min(H,600), vx:0, vy:0 }));
|
|
365
|
+
}
|
|
366
|
+
const E = DATA.edges.filter(e => N.has(e.from) && N.has(e.to));
|
|
367
|
+
|
|
368
|
+
let cam = { x:0, y:0, z:1 };
|
|
369
|
+
let forces = { repel:2200, dist:120, center:0.008 };
|
|
370
|
+
document.getElementById('repel').oninput = e => forces.repel = +e.target.value;
|
|
371
|
+
document.getElementById('dist').oninput = e => forces.dist = +e.target.value;
|
|
372
|
+
document.getElementById('center').oninput = e => forces.center = +e.target.value / 1000;
|
|
373
|
+
|
|
374
|
+
let drag=null, hover=null, panning=false, last={x:0,y:0}, query='', selected=null;
|
|
375
|
+
function radius(n){ return (n.kind==='index'?7:5) + Math.min(n.inbound*1.4, 10); }
|
|
376
|
+
|
|
377
|
+
function step(){
|
|
378
|
+
const nodes = [...N.values()];
|
|
379
|
+
for (let i=0;i<nodes.length;i++){ const a=nodes[i];
|
|
380
|
+
for (let j=i+1;j<nodes.length;j++){ const b=nodes[j];
|
|
381
|
+
let dx=a.x-b.x, dy=a.y-b.y, d2=dx*dx+dy*dy||1, d=Math.sqrt(d2);
|
|
382
|
+
const f=forces.repel/d2, fx=dx/d*f, fy=dy/d*f; a.vx+=fx; a.vy+=fy; b.vx-=fx; b.vy-=fy; } }
|
|
383
|
+
for (const e of E){ const a=N.get(e.from), b=N.get(e.to);
|
|
384
|
+
let dx=b.x-a.x, dy=b.y-a.y, d=Math.hypot(dx,dy)||1;
|
|
385
|
+
const f=(d-forces.dist)*0.015, fx=dx/d*f, fy=dy/d*f; a.vx+=fx; a.vy+=fy; b.vx-=fx; b.vy-=fy; }
|
|
386
|
+
for (const n of nodes){ if(n===drag)continue;
|
|
387
|
+
n.vx+=(W/2-n.x)*forces.center; n.vy+=(H/2-n.y)*forces.center; n.x+=(n.vx*=0.82); n.y+=(n.vy*=0.82); }
|
|
388
|
+
}
|
|
389
|
+
function toScreen(n){ return { x:(n.x-W/2)*cam.z+W/2+cam.x, y:(n.y-H/2)*cam.z+H/2+cam.y }; }
|
|
390
|
+
function fromScreen(px,py){ return { x:(px-W/2-cam.x)/cam.z+W/2, y:(py-H/2-cam.y)/cam.z+H/2 }; }
|
|
391
|
+
|
|
392
|
+
function draw(){
|
|
393
|
+
x.clearRect(0,0,W,H); x.lineWidth=1;
|
|
394
|
+
for (const e of E){ const na=N.get(e.from), nb=N.get(e.to), a=toScreen(na), b=toScreen(nb);
|
|
395
|
+
const hot=selected&&(e.from===selected.id||e.to===selected.id);
|
|
396
|
+
const g=na.kind==='ghost'||nb.kind==='ghost';
|
|
397
|
+
x.strokeStyle = hot?'rgba(167,139,250,.55)':(g?'rgba(135,133,147,.16)':'rgba(135,133,147,.30)');
|
|
398
|
+
x.beginPath(); x.moveTo(a.x,a.y); x.lineTo(b.x,b.y); x.stroke(); }
|
|
399
|
+
for (const n of N.values()){
|
|
400
|
+
const p=toScreen(n), r=radius(n)*cam.z, col=nodeColor(n);
|
|
401
|
+
const match=query&&(n.title.toLowerCase().includes(query)||n.id.toLowerCase().includes(query));
|
|
402
|
+
const active=n===hover||n===selected||match;
|
|
403
|
+
if (n.kind==='ghost'){
|
|
404
|
+
x.beginPath(); x.arc(p.x,p.y,r,0,7); x.fillStyle='rgba(10,10,10,.6)'; x.fill();
|
|
405
|
+
x.lineWidth=1.5; x.setLineDash([3,3]); x.strokeStyle=col; x.globalAlpha=.85; x.stroke(); x.setLineDash([]); x.globalAlpha=1;
|
|
406
|
+
} else { x.beginPath(); x.arc(p.x,p.y,r,0,7); x.fillStyle=col; x.fill(); }
|
|
407
|
+
if (active){ x.lineWidth=2; x.strokeStyle='#fff'; x.beginPath(); x.arc(p.x,p.y,r+1.5,0,7); x.stroke(); }
|
|
408
|
+
// Source logo (data: URI) drawn inside the node when zoomed in and loaded.
|
|
409
|
+
const iconSrc=nodeIcon(n);
|
|
410
|
+
if (iconSrc && cam.z>0.5 && n.kind!=='ghost'){
|
|
411
|
+
const img=getIcon(iconSrc);
|
|
412
|
+
if (img && img.complete && img.naturalWidth){ const s=r*1.3; x.save(); x.beginPath(); x.arc(p.x,p.y,r,0,7); x.clip(); x.drawImage(img,p.x-s/2,p.y-s/2,s,s); x.restore(); }
|
|
413
|
+
}
|
|
414
|
+
if (cam.z>0.6 || active){
|
|
415
|
+
x.fillStyle = active?'#fff':'#878593';
|
|
416
|
+
x.font = (n.kind==='index'?'600 ':'')+(11*Math.min(cam.z,1.3))+'px "Noto Sans",sans-serif';
|
|
417
|
+
x.textAlign='center'; x.fillText(n.title, p.x, p.y+r+12); }
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
(function loop(){ step(); draw(); requestAnimationFrame(loop); })();
|
|
421
|
+
|
|
422
|
+
const panel=document.getElementById('panel'), panelIn=document.getElementById('panel-in');
|
|
423
|
+
function mdToHtml(md){
|
|
424
|
+
md = md.replace(/^---\n[\s\S]*?\n---\n?/, '');
|
|
425
|
+
return md.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
|
426
|
+
.replace(/^### (.*)$/gm,'<h3>$1</h3>').replace(/^## (.*)$/gm,'<h2>$1</h2>').replace(/^# (.*)$/gm,'<h1>$1</h1>')
|
|
427
|
+
.replace(/^> (.*)$/gm,'<blockquote>$1</blockquote>').replace(/^---$/gm,'<hr>')
|
|
428
|
+
.replace(/\*\*([^*]+)\*\*/g,'<b>$1</b>').replace(/` + "`([^`]+)`" + String.raw `/g,'<code>$1</code>')
|
|
429
|
+
.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g,(m,t,a)=>'<a data-link="'+t.trim()+'">'+(a||t).trim()+'</a>')
|
|
430
|
+
.replace(/\n\n/g,'<br><br>').replace(/\n/g,'<br>');
|
|
431
|
+
}
|
|
432
|
+
function openNode(n){
|
|
433
|
+
selected=n; const col=nodeColor(n);
|
|
434
|
+
const catLabel = n.kind==='index'?'INDEX':(n.category||'note').toUpperCase();
|
|
435
|
+
let h='<div class="p-close" onclick="closePanel()">×</div>';
|
|
436
|
+
h+='<div class="p-head"><span class="p-dot" style="'+(n.kind==='ghost'?'background:transparent;border:1.5px dashed '+col:'background:'+col)+'"></span><span class="p-title">'+n.title+'</span></div>';
|
|
437
|
+
h+='<span class="p-cat" style="color:'+col+';background:'+col+'22">'+catLabel+'</span>';
|
|
438
|
+
if(n.kind==='ghost') h+='<div class="p-ghost">⊘ Not written yet — a [[link]] points here, but no note exists. A gap to fill.</div>';
|
|
439
|
+
if(n.tags&&n.tags.length) h+='<div class="p-tags">'+n.tags.map(t=>'<span class="p-tag">#'+t+'</span>').join('')+'</div>';
|
|
440
|
+
if(n.content) h+='<div class="p-md">'+mdToHtml(n.content)+'</div>';
|
|
441
|
+
const out=E.filter(e=>e.from===n.id).map(e=>e.to), inc=E.filter(e=>e.to===n.id).map(e=>e.from);
|
|
442
|
+
if(out.length||inc.length){ h+='<div class="p-links">';
|
|
443
|
+
if(out.length) h+='<h4>Links to</h4>'+out.map(id=>'<a data-link="'+id+'">'+(N.get(id)?N.get(id).title:id)+'</a>').join('');
|
|
444
|
+
if(inc.length) h+='<h4 style="margin-top:12px">Linked from</h4>'+inc.map(id=>'<a data-link="'+id+'">'+(N.get(id)?N.get(id).title:id)+'</a>').join('');
|
|
445
|
+
h+='</div>'; }
|
|
446
|
+
panelIn.innerHTML=h; panel.classList.add('open'); setTimeout(size,210);
|
|
447
|
+
panelIn.querySelectorAll('a[data-link]').forEach(a=>{ a.onclick=()=>{ const t=N.get(a.getAttribute('data-link')); if(t)openNode(t); }; });
|
|
448
|
+
}
|
|
449
|
+
function closePanel(){ selected=null; panel.classList.remove('open'); setTimeout(size,210); }
|
|
450
|
+
window.closePanel=closePanel;
|
|
451
|
+
|
|
452
|
+
const tip=document.getElementById('tip');
|
|
453
|
+
let downAt=null, moved=false;
|
|
454
|
+
function pick(px,py){ const w=fromScreen(px,py); let best=null,bd=1e9;
|
|
455
|
+
for (const n of N.values()){ const d=Math.hypot(n.x-w.x,n.y-w.y); if(d<bd&&d<radius(n)/cam.z+8){bd=d;best=n;} } return best; }
|
|
456
|
+
c.addEventListener('mousedown', e=>{ const n=pick(e.clientX,e.clientY); moved=false; downAt={x:e.clientX,y:e.clientY};
|
|
457
|
+
if(n){drag=n;}else{panning=true; last={x:e.clientX,y:e.clientY};} });
|
|
458
|
+
addEventListener('mousemove', e=>{
|
|
459
|
+
if(downAt && Math.hypot(e.clientX-downAt.x,e.clientY-downAt.y)>4) moved=true;
|
|
460
|
+
if(drag){ const w=fromScreen(e.clientX,e.clientY); drag.x=w.x; drag.y=w.y; drag.vx=drag.vy=0; }
|
|
461
|
+
else if(panning){ cam.x+=e.clientX-last.x; cam.y+=e.clientY-last.y; last={x:e.clientX,y:e.clientY}; }
|
|
462
|
+
else { hover=pick(e.clientX,e.clientY);
|
|
463
|
+
if(hover){ const col=nodeColor(hover); tip.classList.add('show'); tip.style.left=(e.clientX+14)+'px'; tip.style.top=(e.clientY+14)+'px';
|
|
464
|
+
tip.innerHTML='<div class="t">'+hover.title+'</div><div class="cat" style="color:'+col+'">'+(hover.kind==='index'?'index':(hover.category||'note'))+(hover.kind==='ghost'?' · not written':'')+'</div>'+(hover.tags&&hover.tags.length?'<div class="tags">#'+hover.tags.slice(0,6).join(' #')+'</div>':'');
|
|
465
|
+
c.style.cursor='pointer';
|
|
466
|
+
} else { tip.classList.remove('show'); c.style.cursor='grab'; } }
|
|
467
|
+
});
|
|
468
|
+
addEventListener('mouseup', e=>{ if(drag&&!moved) openNode(drag); drag=null; panning=false; downAt=null; });
|
|
469
|
+
c.addEventListener('wheel', e=>{ e.preventDefault(); const f=e.deltaY<0?1.1:0.9; cam.z=Math.max(0.2,Math.min(4,cam.z*f)); }, {passive:false});
|
|
470
|
+
document.getElementById('q').addEventListener('input', e=>{ query=e.target.value.toLowerCase().trim(); });
|
|
471
|
+
</script>
|
|
472
|
+
</body></html>`;
|
|
473
|
+
//# sourceMappingURL=brain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brain.js","sourceRoot":"","sources":["../../src/visualizer/brain.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,5 +13,23 @@ import type { TopologyAST } from "../parser/ast.js";
|
|
|
13
13
|
*
|
|
14
14
|
* Save the returned string to a `.html` file and open it in any browser.
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
/** A brain store this topology owns, with where its graph was rendered. */
|
|
17
|
+
export interface BrainLinkRef {
|
|
18
|
+
/** The store id (e.g. "brain", "client-brain"). */
|
|
19
|
+
id: string;
|
|
20
|
+
/** Relative href to the brain graph HTML. */
|
|
21
|
+
href: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Options for the topology visualization.
|
|
25
|
+
*/
|
|
26
|
+
export interface VisualizationOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Brain stores this topology declares (`type: brain`) that were visualized
|
|
29
|
+
* alongside it. One → an "Open Brain →" button; several → a dropdown listing
|
|
30
|
+
* each brain by name. A topology can own multiple brains.
|
|
31
|
+
*/
|
|
32
|
+
brains?: BrainLinkRef[];
|
|
33
|
+
}
|
|
34
|
+
export declare function generateVisualization(ast: TopologyAST, opts?: VisualizationOptions): string;
|
|
17
35
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/visualizer/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EACV,WAAW,EASZ,MAAM,kBAAkB,CAAC;AA8C1B;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/visualizer/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EACV,WAAW,EASZ,MAAM,kBAAkB,CAAC;AA8C1B;;;;GAIG;AACH,2EAA2E;AAC3E,MAAM,WAAW,YAAY;IAC3B,mDAAmD;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,GAAE,oBAAyB,GAAG,MAAM,CAK/F"}
|