@fangorn-network/westmarch 0.0.1-dev → 0.0.2-dev
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 +2 -2
- package/consume/apps.js +4 -2
- package/consume/corpora.js +1 -1
- package/consume/directory.js +1 -1
- package/consume/embed.js +1 -1
- package/consume/lint.js +1 -1
- package/consume/mcp-bin.js +6 -0
- package/consume/mcp.js +193 -34
- package/consume/ort-preload.js +25 -0
- package/consume/rank.js +1 -1
- package/consume/roles.js +6 -4
- package/consume/shard.js +1 -1
- package/consume/steam.js +1 -1
- package/consume/taste-doc.js +1 -1
- package/consume/taste.js +1 -1
- package/consume/tools.js +28 -5
- package/consume/ui.js +1 -1
- package/consume/view-tools.js +202 -0
- package/consume/x402.js +94 -0
- package/docs/app-to-agent.md +553 -185
- package/package.json +11 -4
- package/publish/agent-card.js +12 -2
- package/publish/cohort.js +1 -1
- package/publish/demand.js +1 -1
- package/publish/enrich.js +1 -1
- package/publish/envelope.js +1 -1
- package/publish/graph.js +1 -1
- package/publish/manifest.js +1 -1
- package/publish/reactions.js +4 -2
- package/publish/settle.js +1 -1
- package/publish/ship.js +378 -0
- package/publish/terms.js +1 -1
- package/publish/vault.js +1 -1
- package/publish/view-cli.js +39 -0
- package/publish/view.js +399 -0
- package/site/agent.js +36 -0
- package/site/dist/assets/__vite-browser-external-CKnVkMRn.js +1 -0
- package/site/dist/assets/embed.worker-CQqmvS52.js +1 -0
- package/site/dist/assets/index-DlzVyQ_9.js +6 -0
- package/site/dist/assets/ort-wasm-simd-threaded.asyncify-DMmc6YqF.wasm +0 -0
- package/site/dist/assets/preload-helper-uBIymjUX.js +1 -0
- package/site/dist/assets/roles-C9V_uDr_.js +1 -0
- package/site/dist/assets/roles-YDRjv_PY.js +1 -0
- package/site/dist/assets/rolldown-runtime-Dd_uD5pT.js +1 -0
- package/site/dist/assets/transformers.web-BgUm7F_-.js +46 -0
- package/site/dist/assets/transformers.web-u9uSoh5E.js +37 -0
- package/site/dist/index.html +20 -0
- package/site/feed.js +62 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<script type="module" crossorigin src="./assets/index-DlzVyQ_9.js"></script>
|
|
4
|
+
<link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-Dd_uD5pT.js">
|
|
5
|
+
<link rel="modulepreload" crossorigin href="./assets/preload-helper-uBIymjUX.js">
|
|
6
|
+
<link rel="modulepreload" crossorigin href="./assets/roles-C9V_uDr_.js">
|
|
7
|
+
|
|
8
|
+
<meta charset="utf-8">
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
10
|
+
<title>Fangorn app</title>
|
|
11
|
+
<style>:root{--bg:#fbfaf7;--card:#fff;--fg:#1d1d1b;--muted:#6b6a66;--accent:#2b5d8a;--line:#e4e1da;--chip:#efece5}@media (prefers-color-scheme:dark){:root{--bg:#151514;--card:#1c1c1a;--fg:#ecebe6;--muted:#9a988f;--accent:#7fb0dc;--line:#2c2b28;--chip:#262522}}body{background:var(--bg);color:var(--fg);max-width:800px;margin:0 auto;padding:32px 16px;font:16px/1.5 system-ui,sans-serif}h1{letter-spacing:-.01em;margin:0 0 4px;font-size:1.7rem}#about{color:var(--muted);margin:0 0 20px}input{box-sizing:border-box;width:100%;font:inherit;border:1px solid var(--line);background:var(--card);color:inherit;border-radius:10px;padding:11px 14px}#status{color:var(--muted);margin:8px 0 12px;font-size:.85rem}#chips{flex-wrap:wrap;gap:6px;margin-bottom:20px;display:flex}button{font:inherit;cursor:pointer}.chip{border:1px solid var(--line);background:var(--chip);color:var(--fg);border-radius:999px;padding:4px 12px;font-size:.88rem}.chip.on{background:var(--accent);border-color:var(--accent);color:var(--bg)}h2{text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin:28px 0 10px;font-size:.8rem}article{background:var(--card);border:1px solid var(--line);border-radius:12px;margin-bottom:12px;padding:14px 16px}article header{flex-direction:column;margin-bottom:6px;display:flex}article time{color:var(--muted);font-size:.8rem}h3{margin:0;font-size:1.02rem}ul{margin:0;padding:0;list-style:none}li{border-top:1px solid var(--line);overflow-wrap:anywhere;flex-direction:column;gap:2px;padding:8px 0;display:flex}article li:first-child,#results li:first-child{border-top:0}.detail{color:var(--muted);margin:0;font-size:.9rem}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}small{color:var(--muted)}.more{color:var(--accent);background:0 0;border:0;padding:6px 0 0;font-size:.9rem}.empty{color:var(--muted)}footer{color:var(--muted);margin-top:32px;font-size:.8rem}</style>
|
|
12
|
+
<h1>Fangorn app</h1>
|
|
13
|
+
<p id="about"></p>
|
|
14
|
+
<input type="search" autofocus disabled placeholder="Loading…" aria-label="Search">
|
|
15
|
+
<div id="status">Loading the data…</div>
|
|
16
|
+
<div id="chips"></div>
|
|
17
|
+
<ul id="results" hidden></ul>
|
|
18
|
+
<main id="feed"></main>
|
|
19
|
+
<footer>Search runs in your browser; your query is never sent anywhere. Agents: <a href="./.well-known/agent-card.json">agent card</a>.</footer>
|
|
20
|
+
</html>
|
package/site/feed.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// The front page without a query: what is coming up and what just happened, one card per
|
|
2
|
+
// occasion (a meeting, an issue, an episode), from the roles the app declared. Nothing here
|
|
3
|
+
// knows what a row is: an occasion is the rows sharing a subtitle and a facet value, and
|
|
4
|
+
// its date is their temporal field.
|
|
5
|
+
import { pick, subtitleOf, titleOf, values } from "../consume/roles.js";
|
|
6
|
+
|
|
7
|
+
/** The field a reader narrows by first: the app's declared facet, else the first tag with
|
|
8
|
+
* a handful of values (a town, not every body in every town). */
|
|
9
|
+
export function facetField(rows, roles) {
|
|
10
|
+
const count = (f) => new Set(rows.slice(0, 5000).flatMap((r) => values(r[f] ?? ""))).size;
|
|
11
|
+
return roles.facets?.[0] ?? roles.tags.find((f) => { const n = count(f); return n >= 2 && n <= 12; }) ?? null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Occasions: `{ key, date, place, label, items }`, split into upcoming (soonest first) and
|
|
15
|
+
* past (latest first). Items repeat across an occasion's documents (its agenda, its
|
|
16
|
+
* minutes): one per title, the one that says what happened. */
|
|
17
|
+
export function occasions(rows, roles, { facet = null, only = null, today = new Date().toISOString().slice(0, 10) } = {}) {
|
|
18
|
+
const groups = new Map();
|
|
19
|
+
for (const r of rows) {
|
|
20
|
+
const date = String(pick(r, roles.temporal) ?? "").slice(0, 10);
|
|
21
|
+
if (!date) continue;
|
|
22
|
+
const place = facet ? String(r[facet] ?? "") : "";
|
|
23
|
+
if (only && place !== only) continue;
|
|
24
|
+
const label = subtitleOf(r, roles) ?? "";
|
|
25
|
+
const key = `${place}|${label}|${date}`;
|
|
26
|
+
let g = groups.get(key);
|
|
27
|
+
if (!g) groups.set(key, (g = { key, date, place, label, byTitle: new Map() }));
|
|
28
|
+
const t = titleOf(r, roles), k = t.toLowerCase();
|
|
29
|
+
const had = g.byTitle.get(k);
|
|
30
|
+
if (!had || detail(had, roles).length < detail(r, roles).length) g.byTitle.set(k, r);
|
|
31
|
+
}
|
|
32
|
+
const all = [...groups.values()].map(({ byTitle, ...g }) => ({ ...g, items: [...byTitle.values()] }));
|
|
33
|
+
return {
|
|
34
|
+
upcoming: all.filter((g) => g.date > today).sort((a, b) => a.date.localeCompare(b.date) || a.place.localeCompare(b.place)),
|
|
35
|
+
past: all.filter((g) => g.date <= today).sort((a, b) => b.date.localeCompare(a.date) || a.place.localeCompare(b.place)),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** What a row says beyond its title: its text, less the title it opens with. */
|
|
40
|
+
export function detail(r, roles) {
|
|
41
|
+
const t = titleOf(r, roles), x = String(r.text ?? "");
|
|
42
|
+
const rest = x.startsWith(t.replace(/\.$/, "")) ? x.slice(t.replace(/\.$/, "").length).replace(/^[.\s]+/, "") : x;
|
|
43
|
+
return rest === t ? "" : rest.trim();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── self-check: `node site/feed.js` ──
|
|
47
|
+
if (typeof process !== "undefined" && process.argv[1]?.endsWith("/feed.js")) {
|
|
48
|
+
const { rolesFrom } = await import("../consume/roles.js");
|
|
49
|
+
const roles = rolesFrom([{ role_map: { title: "heading", subtitle: "meeting", temporal: "date", tags: ["city", "body"], text: ["text"] },
|
|
50
|
+
presentation: { facets: ["city"] } }]);
|
|
51
|
+
const row = (city, date, heading, text = heading) => ({ city, date, heading, meeting: `Board · ${date}`, body: "Board", text });
|
|
52
|
+
const rows = [row("A", "2026-01-01", "Budget"), row("A", "2026-01-01", "Budget", "Budget. Approved 5-0."), row("A", "2026-01-01", "Roads"),
|
|
53
|
+
row("B", "2026-02-01", "Parks"), row("A", "2026-03-01", "Zoning"), { heading: "undated" }];
|
|
54
|
+
const eq = (a, b, m) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${m}: ${JSON.stringify(a)}`); };
|
|
55
|
+
eq(facetField(rows, roles), "city", "the declared facet wins");
|
|
56
|
+
const { upcoming, past } = occasions(rows, roles, { facet: "city", today: "2026-02-15" });
|
|
57
|
+
eq(upcoming.map((g) => g.key), ["A|Board · 2026-03-01|2026-03-01"], "a future date is coming up");
|
|
58
|
+
eq(past.map((g) => `${g.place}${g.date}`), ["B2026-02-01", "A2026-01-01"], "past, latest first");
|
|
59
|
+
eq(past[1].items.map((r) => detail(r, roles)), ["Approved 5-0.", ""], "one item per title, the one that says what happened");
|
|
60
|
+
eq(occasions(rows, roles, { facet: "city", only: "B", today: "2026-02-15" }).past.length, 1, "narrowed to one place");
|
|
61
|
+
console.log("feed.js self-check ok — declared facet, upcoming vs past, one item per title keeping the decision, narrowed by place");
|
|
62
|
+
}
|