@fangorn-network/westmarch 0.1.1 → 0.1.2

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.
@@ -49,7 +49,8 @@ update: each step checks the chain (or Cloudflare) first and acts only on a diff
49
49
  "tags": ["category"], "text": ["summary"] },
50
50
  "presentation": { "facets": ["category"] } } },
51
51
  "relations": [],
52
- "sources": [{ "namespace": "things", "command": ["python", "-m", "my_source"] }],
52
+ "sources": [{ "namespace": "things", "command": ["python", "-m", "my_source"] }], // your wallet's repos only
53
+ "live": ["readings"], // optional: namespaces other wallets push to; "0x<wallet>/readings" for one wallet's
53
54
  "network": "baseSepolia", // required
54
55
  "fangorn": "fangorn", // the CLI that signs
55
56
  "paid": { "price": "0.01", "description": "the full record" } // optional: USDC on the app's network, x402 v2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fangorn-network/westmarch",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "all-in-one builder for fangorn apps",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -90,5 +90,6 @@
90
90
  },
91
91
  "publishConfig": {
92
92
  "access": "public"
93
- }
93
+ },
94
+ "packageManager": "pnpm@10.30.1+sha512.3590e550d5384caa39bd5c7c739f72270234b2f6059e13018f975c313b1eb9fefcc09714048765d4d9efe961382c312e624572c0420762bdc5d5940cdf9be73a"
94
95
  }
@@ -38,6 +38,8 @@ export async function captureTools(register) {
38
38
  * tools. With it, `{app, owner, fromBlock, repos, views, mcp, config}`:
39
39
  * app the app's repo name
40
40
  * owner the address that publishes it
41
+ * repos the repos the app presents: `0x<owner>/<repo>` (that publisher's) or a
42
+ * bare `<repo>` (any publisher's); [] for every repo
41
43
  * fromBlock a block at or before the app's registration. The reader's log
42
44
  * scan starts here; leave it at 0 and a reader makes one RPC call
43
45
  * per 1000 blocks of chain history.
@@ -31,10 +31,10 @@
31
31
 
32
32
  import { spawn, spawnSync } from "node:child_process";
33
33
  import { createHash } from "node:crypto";
34
- import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
34
+ import { cpSync, existsSync, mkdtempSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
35
35
  import { createRequire } from "node:module";
36
36
  import { dirname, join, relative, resolve } from "node:path";
37
- import { homedir } from "node:os";
37
+ import { homedir, tmpdir } from "node:os";
38
38
  import { fileURLToPath, pathToFileURL } from "node:url";
39
39
 
40
40
  const HERE = dirname(fileURLToPath(import.meta.url));
@@ -63,9 +63,11 @@ const log = (s) => console.error(`[ship] ${s}`);
63
63
  // "sources": [{ "namespace": "…", "cwd": "…", "command": ["python", "-m", "…", …] }],
64
64
  // "live": ["readings"], // optional: namespaces members push to from their own wallets (devices,
65
65
  // // contributors), presented beside the sources'; true = every repo they push.
66
+ // // A bare name takes any wallet's repo of it; "0x<wallet>/readings" takes only
67
+ // // that wallet's. Sources are always the app's own wallet's repos.
66
68
  // // Without sources the app already presents every member's repo.
67
69
  // "network": "baseSepolia", // required: the SDK's network name
68
- // "owner": "0x…", // the wallet that publishes the app's repo; only `westmarch build` (the
70
+ // "owner": "0x…", // the wallet that publishes the app's repo and its sources; only `westmarch build` (the
69
71
  // // deploy button) needs it, westmarch-ship asks the CLI
70
72
  // "fangorn": "fangorn", // the CLI that signs; e.g. "node ~/fangorn/fangorn/lib/cli/cli.js",
71
73
  // // or "env HOME=<dir> node …/cli.js" to sign with <dir>/.fangorn/config.json
@@ -111,16 +113,17 @@ export function loadConfig(path) {
111
113
  if (!c.site.project) throw new Error(`${path}: site.project names the Cloudflare Worker`);
112
114
  for (const s of c.sources) if (!s.namespace || !Array.isArray(s.command))
113
115
  throw new Error(`${path}: each source needs a namespace and a command (an argv array)`);
114
- if (c.live != null && c.live !== true && !(Array.isArray(c.live) && c.live.every((n) => typeof n === "string")))
115
- throw new Error(`${path}: live is true (every repo members push) or a list of the namespaces they push to`);
116
+ if (c.live != null && c.live !== true && !(Array.isArray(c.live) && c.live.every((n) => typeof n === "string" && n && !/^0x(?![0-9a-fA-F]{40}\/.)/.test(n))))
117
+ throw new Error(`${path}: live is true (every repo members push) or a list of the namespaces they push to: "name" (any wallet's) or "0x<40 hex>/name" (that wallet's)`);
116
118
  return { relations: [], tags: [], types: {}, ...c };
117
119
  }
118
120
 
119
- /** The repo names the view presents: the sources' namespaces plus `live`'s, the ones members push
120
- * to themselves (devices, contributors). [] means every repo the app presents: no sources, or live: true. */
121
- export function presentedRepos(cfg) {
121
+ /** The repos the view presents (view.js `presents`): the sources' namespaces, pinned to `owner`, the
122
+ * wallet that publishes them, plus `live`'s, the ones members push to themselves (devices, contributors).
123
+ * [] means every repo the app presents: no sources, or live: true. */
124
+ export function presentedRepos(cfg, owner) {
122
125
  if (cfg.live === true) return [];
123
- return [...new Set([...cfg.sources.map((s) => s.namespace), ...(cfg.live ?? [])])];
126
+ return [...new Set([...cfg.sources.map((s) => `${owner.toLowerCase()}/${s.namespace}`), ...(cfg.live ?? [])])];
124
127
  }
125
128
 
126
129
  /** The Worker's wrangler config: the stock worker, the site as its assets, its bucket, the AI
@@ -225,11 +228,13 @@ export async function buildForDeploy(configPath, wranglerPath = join(dirname(res
225
228
 
226
229
  const base = dirname(resolve(configPath));
227
230
  const { site, writeCard } = await buildSite(cfg, base);
228
- writeCard(view.ORIGIN, { owner: cfg.owner, fromBlock, repos: [] });
231
+ // Sources pinned to the owner beside `live`, as a ship presents them; [] (every repo) with neither, or live: true.
232
+ const repos = presentedRepos(cfg, cfg.owner);
233
+ writeCard(view.ORIGIN, { owner: cfg.owner, fromBlock, repos });
229
234
  const gateway = process.env.FANGORN_IPFS_GATEWAY;
230
235
  w.assets = { ...w.assets, directory: relative(dirname(resolve(wranglerPath)), site) };
231
236
  w.vars = { ...w.vars, CONFIG: { app: cfg.app, name: cfg.name, description: cfg.description, fromBlock, network: cfg.network,
232
- schema: { types: cfg.types, relations: cfg.relations }, repos: [], paid: false },
237
+ schema: { types: cfg.types, relations: cfg.relations }, repos, paid: false },
233
238
  ...(gateway ? { IPFS_GATEWAY: gateway } : {}) };
234
239
  writeFileSync(wranglerPath, JSON.stringify(w, null, 1) + "\n");
235
240
  log(`built ${cfg.app} (fromBlock ${fromBlock}) into ${site}; wrangler deploy ships it`);
@@ -266,7 +271,7 @@ export async function ship(configPath, { crawl = true, deploy = true, dry = fals
266
271
  const repoDir = join(dir, "repo");
267
272
  if (!(await fangorn.onChainTip(me, cfg.app))) {
268
273
  // Its own records start now; an app presenting every repo (the explorer) reads from the deployment's start.
269
- if (presentedRepos(cfg).length) state.fromBlock = String(await chain.getBlockNumber());
274
+ if (presentedRepos(cfg, me).length) state.fromBlock = String(await chain.getBlockNumber());
270
275
  log(`creating the app's repo ${me}:${cfg.app}`);
271
276
  mkdirSync(repoDir, { recursive: true });
272
277
  fangornCli(["repo", "init", cfg.app], { cwd: repoDir, dry });
@@ -295,7 +300,7 @@ export async function ship(configPath, { crawl = true, deploy = true, dry = fals
295
300
  // site.url: the app's own domain, routed to the Worker. Otherwise its workers.dev URL,
296
301
  // which is only known once it is deployed: the first deploy learns it and the card follows.
297
302
  let url = cfg.site.url?.replace(/\/$/, "") ?? state.url ?? null;
298
- const repos = presentedRepos(cfg);
303
+ const repos = presentedRepos(cfg, me);
299
304
  const writeSite = (u) => writeCard(u, { owner: me, fromBlock: state.fromBlock, repos, paid: !!cfg.paid });
300
305
  writeSite(url ?? `https://${cfg.site.project}.workers.dev`);
301
306
 
@@ -470,7 +475,7 @@ export async function devnetShip(configPath, { crawl = false, only = null, port
470
475
  }
471
476
 
472
477
  const url = `http://127.0.0.1:${port}`;
473
- const repos = Object.keys(staged);
478
+ const repos = Object.keys(staged).map((ns) => `${owner.getAddress().toLowerCase()}/${ns}`);
474
479
  const { site, writeCard } = await buildSite(cfg, base);
475
480
  writeCard(url, { owner: owner.getAddress(), fromBlock: "0", repos, paid: !!services });
476
481
  const wcfg = wranglerConfig({ name: cfg.site.project, site, config: { app: cfg.app, name: cfg.name, description: cfg.description,
@@ -583,9 +588,10 @@ export async function buildLocal(configPath, out, { crawl = false, only = null,
583
588
  const statePath = join(base, ".ship/state.json");
584
589
  const fromBlock = (existsSync(statePath) ? JSON.parse(readFileSync(statePath, "utf8")).fromBlock : null) ?? cfg.fromBlock;
585
590
  const staged = new Set(cfg.sources.map((s) => s.namespace));
591
+ const { presents } = view;
586
592
  for (const t of await fangorn.repos(fromBlock == null ? {} : { fromBlock: BigInt(fromBlock) })) {
587
593
  if (t.repo === "fangorn.schema" || staged.has(t.repo) || (only && !only.includes(t.repo))
588
- || (cfg.live !== true && !cfg.live.includes(t.repo))) continue;
594
+ || (cfg.live !== true && !presents(cfg.live, t.owner, t.repo))) continue;
589
595
  const { contents } = await fangorn.readRepo(t.owner, t.repo);
590
596
  chain[`${t.owner.toLowerCase()}/${t.repo}`] = { records: contents.records };
591
597
  say(`live ${t.owner}/${t.repo}: ${contents.records.length} records, from the chain`);
@@ -595,7 +601,7 @@ export async function buildLocal(configPath, out, { crawl = false, only = null,
595
601
  rmSync(out, { recursive: true, force: true });
596
602
  mkdirSync(join(base, ".ship"), { recursive: true });
597
603
  const embed = view.cachedEmbed(join(base, ".ship/vectors.ndjson"));
598
- const repos = presentedRepos(cfg).filter((n) => !only || only.includes(n));
604
+ const repos = presentedRepos(cfg, owner).filter((n) => !only || only.includes(n.replace(/^0x[0-9a-f]{40}\//i, "")));
599
605
  const report = await view.publishView({ fangorn: view.localFangorn(chain), app: cfg.app,
600
606
  schema: { types: cfg.types, relations: cfg.relations }, repos, out, embed, log: say });
601
607
  const rows = Object.values(report).reduce((n, r) => n + r.added, 0);
@@ -615,10 +621,15 @@ if (process.argv[1]?.endsWith("ship.js") || process.argv[1]?.endsWith("westmarch
615
621
  || !w.compatibility_flags.includes("nodejs_compat") || !existsSync(w.main) || !existsSync(w.alias["@huggingface/transformers"]))
616
622
  throw new Error(`wrangler config: ${JSON.stringify(w)}`);
617
623
  const src = [{ namespace: "towns", command: ["py"] }];
618
- if (presentedRepos({ sources: src }).join() !== "towns" || presentedRepos({ sources: src, live: ["readings", "towns"] }).join() !== "towns,readings"
619
- || presentedRepos({ sources: src, live: true }).length || presentedRepos({ sources: [] }).length)
620
- throw new Error("presentedRepos: sources plus live namespaces; [] (every repo) for live: true or no sources");
621
- console.log("ship.js self-check ok — a source template fills per row, the Worker config binds its bucket, AI and cron, and presentedRepos merges sources and live namespaces");
624
+ const O = `0x${"Ab".repeat(20)}`, o = O.toLowerCase(), M = `0x${"cd".repeat(20)}`;
625
+ if (presentedRepos({ sources: src }, O).join() !== `${o}/towns` || presentedRepos({ sources: src, live: ["readings", `${M}/towns`] }, O).join() !== `${o}/towns,readings,${M}/towns`
626
+ || presentedRepos({ sources: src, live: true }, O).length || presentedRepos({ sources: [] }, O).length)
627
+ throw new Error("presentedRepos: sources pinned to the owner plus live namespaces as written; [] (every repo) for live: true or no sources");
628
+ const tmp = join(mkdtempSync(join(tmpdir(), "ship-")), "app.json");
629
+ const takes = (live) => { writeFileSync(tmp, JSON.stringify({ app: "a", name: "a", description: "d", site: { project: "a" }, live })); try { return !!loadConfig(tmp); } catch { return false; } };
630
+ if (![takes(["readings"]), takes([`${M}/readings`]), takes(true), !takes(["0x12/readings"]), !takes([`${M}/`]), !takes([""])].every(Boolean))
631
+ throw new Error("live: a bare name or 0x<40 hex>/name, nothing half-qualified");
632
+ console.log("ship.js self-check ok — a source template fills per row, the Worker config binds its bucket, AI and cron, and presentedRepos pins sources to the owner beside live namespaces, bare or owner-qualified");
622
633
  process.exit(0);
623
634
  }
624
635
  const [path, ...flags] = process.argv.slice(2);
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // westmarch-view: publish what an app presents as a view (see view.js).
3
3
  //
4
- // westmarch-view --app my-app --out site/view [--config app.json] [--repo catalog …] [--from-block N] [--rebake]
4
+ // westmarch-view --app my-app --out site/view [--config app.json] [--repo 0x<owner>/catalog …] [--from-block N] [--rebake]
5
5
  //
6
6
  // The schema (types, relations) is read from --config; without it every type is guessed.
7
7
  // Reads only; needs no key. Deploy <out> with the site, and put its URL in the card's
@@ -20,7 +20,7 @@ for (let i = 2; i < process.argv.length; i++) {
20
20
  if (m[1] === "repo") flags.repo.push(v); else flags[m[1]] = v;
21
21
  }
22
22
  if (flags.help || !flags.app || !flags.out) {
23
- console.error("usage: westmarch-view --app <name|0xid> --out <dir> [--config app.json] [--repo <name> …] [--from-block N] [--rebake]");
23
+ console.error("usage: westmarch-view --app <name|0xid> --out <dir> [--config app.json] [--repo <0xowner/name | name> …] [--from-block N] [--rebake]");
24
24
  process.exit(flags.help ? 0 : 2);
25
25
  }
26
26
 
@@ -1,11 +1,11 @@
1
1
  // An app's presented data → a view any Fangorn reader loads: the embedding lens
2
2
  // (WHITEPAPER_1 §3.4), computed by whoever runs this.
3
3
  //
4
- // westmarch-view --app my-app [--repo catalog …] --out site/view (a laptop, CI)
4
+ // westmarch-view --app my-app [--repo 0x<owner>/catalog …] --out site/view (a laptop, CI)
5
5
  // site/worker.js, on a cron (the free tier)
6
6
  //
7
- // Reads the registry and storage and nothing else: every repo the app presents (any
8
- // publisher's repo of a name it lists), at their latest heads; the schema
7
+ // Reads the registry and storage and nothing else: every repo the app presents (`presents`:
8
+ // `0x<owner>/<repo>` for one publisher's, a bare name for any publisher's), at their latest heads; the schema
9
9
  // (app.json's `types` + `relations`) comes from the caller. Embeds only records the view does not hold
10
10
  // yet, drops removed ones, and writes, through a store (a directory or an R2 bucket):
11
11
  //
@@ -54,6 +54,11 @@ const slug = (s) => (String(s ?? "").replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^
54
54
  /** A domain's name in the view: the publisher's address prefix and the repo. */
55
55
  export const domainFor = (owner, namespace) => `${slug(owner.slice(2, 10))}-${slug(namespace)}`;
56
56
 
57
+ /** Whether an app's `repos` list takes `owner`'s `repo`: an entry is `0x<owner>/<repo>` (that
58
+ * publisher's only) or a bare `<repo>` (any publisher's, for open namespaces). [] takes every repo. */
59
+ export const presents = (repos, owner, repo) =>
60
+ !repos?.length || repos.includes(repo) || repos.some((r) => r.toLowerCase() === `${owner.toLowerCase()}/${repo}`.toLowerCase());
61
+
57
62
  /** The text a row is embedded from, minus the `search_document: ` prefix that
58
63
  * `embedDocumentDirect` adds itself. */
59
64
  export function composeText(fields, rm) {
@@ -279,8 +284,8 @@ const sha256 = (s) => createHash("sha256").update(s).digest("hex");
279
284
 
280
285
  /**
281
286
  * Bring the view up to date with what the app presents. `fangorn` is an SDK client (reads
282
- * only); `schema` is app.json's { types, relations } (a type it leaves out is guessed); the view goes to `store`, or the directory `out`. `repos` narrows it to those repo
283
- * names (every publisher's); without it, every repo. `fromBlock` must be
287
+ * only); `schema` is app.json's { types, relations } (a type it leaves out is guessed); the view goes to `store`, or the directory `out`. `repos` narrows it
288
+ * (`presents`: `0x<owner>/<repo>` for one publisher's, a bare name for anyone's); without it, every repo. `fromBlock` must be
284
289
  * at or before the first update to a repo the app presents, or a repo the scan misses is left out;
285
290
  * left out, the SDK's deployment start. `embedBatch(texts)` embeds many at once where the
286
291
  * encoder batches (a Worker's AI binding); `embedder` is what the manifest records.
@@ -297,11 +302,10 @@ export async function publishView({ fangorn, app, schema, repos, out, store = fs
297
302
  const schemaHash = sha256(JSON.stringify(schema));
298
303
 
299
304
  // Every repo the app presents, at its latest head: one log scan.
300
- // ponytail: open: any publisher's repo of a listed name, and with no names, every repo.
301
- // Read the app's repo of citations instead once the SDK reads citations.
305
+ // ponytail: read the app's repo of citations instead once the SDK reads citations.
302
306
  const listed = (presented ?? await retry(() => fangorn.repos(fromBlock == null ? {} : { fromBlock: BigInt(fromBlock) })))
303
307
  // ponytail: "fangorn.schema" is where apps shipped before 2026-10-22 committed their schema; never data.
304
- .filter((t) => t.repo !== "fangorn.schema" && (!repos?.length || repos.includes(t.repo)));
308
+ .filter((t) => t.repo !== "fangorn.schema" && presents(repos, t.owner, t.repo));
305
309
  log(`${listed.length} repo(s) presented${repos?.length ? ` of ${repos.join(", ")}` : ""}`);
306
310
 
307
311
  const report = {};
@@ -328,7 +332,8 @@ export async function publishView({ fangorn, app, schema, repos, out, store = fs
328
332
  if (live.has(d)) continue;
329
333
  const m = await readJson(store, `cdn/domains/${d}/manifest`, null);
330
334
  const repo = m?.filter?.repo?.[0] ?? m?.filter?.namespace?.[0];
331
- if (repos?.length && !repos.includes(repo)) continue;
335
+ // A name this run doesn't cover (an --only build) is left alone; another publisher's repo of a covered name is dropped.
336
+ if (repos?.length && !repos.some((r) => r.replace(/^0x[0-9a-f]{40}\//i, "") === repo)) continue;
332
337
  await store.del(`cdn/domains/${d}`);
333
338
  report[d] = { added: 0, removed: m?.count ?? 0, dropped: true };
334
339
  log(`${d}: no longer presented by the app; dropped`);
@@ -647,6 +652,20 @@ if (import.meta.url === `file://${globalThis.process?.argv?.[1]}` && process.arg
647
652
  chain[`${PUB}/shop`] = shop;
648
653
  eq((await run())[d], { added: 2, removed: 0 }, "and presenting it again brings it back");
649
654
 
655
+ // Two publishers, one repo name: `0x<owner>/<repo>` takes only that publisher's; a bare name takes both.
656
+ eq([presents([`${PUB}/shop`], PUB, "shop"), presents([`${PUB.toUpperCase().replace("0X", "0x")}/shop`], PUB, "shop"),
657
+ presents([`${PUB}/shop`], OWNER, "shop"), presents(["shop"], OWNER, "shop"), presents([], OWNER, "x")],
658
+ [true, true, false, true, true], "presents: owner-qualified, case-insensitive address, bare name, empty list");
659
+ {
660
+ const twins = { [`${PUB}/shop`]: shop, [`${OWNER}/shop`]: { records: [rec("s1", TAG, { sku: "z9", name: "Squat", about: "not the shop you meant" })] } };
661
+ const o = mkdtempSync(`${tmpdir()}/view-twins-`);
662
+ const go = (repos) => publishView({ fangorn: localFangorn(twins), app: APP, schema, out: o, repos, embed, log: () => {} });
663
+ eq(Object.keys(await go(["shop"])).sort(), [domainFor(OWNER, "shop"), domainFor(PUB, "shop")].sort(), "a bare name presents every publisher's repo of it");
664
+ const r = await go([`${PUB}/shop`]);
665
+ eq([r[domainFor(OWNER, "shop")]?.dropped, existsSync(`${o}/cdn/domains/${domainFor(PUB, "shop")}`)], [true, true],
666
+ "pinning the name to one publisher drops the other's, already indexed");
667
+ }
668
+
650
669
  const setEmbedder = (e) => { const x = manifestOf(out, d); x.embedder = e; x.head = "moved"; writeFileSync(`${out}/cdn/domains/${d}/manifest`, JSON.stringify(x)); };
651
670
  setEmbedder({ ...EMBEDDER, runtime: "transformers.js cuda" });
652
671
  if ((await run())[d]?.error) throw new Error("another runtime of the same q8 model must be accepted");
@@ -766,5 +785,5 @@ if (import.meta.url === `file://${globalThis.process?.argv?.[1]}` && process.arg
766
785
  await r2.del(`cdn/domains/${d}`);
767
786
  eq([...objs.keys()].filter((k) => k.includes(d)), [], "del removes a folder and everything under it");
768
787
  }
769
- console.log("view.js self-check ok — domain names and embedded text are stable, the schema is applied, an unmoved head reads nothing, additions ship as delta shards, removals compact the domain, an excluded repo is dropped and a restored one returns, the real reader sees exactly the live records by the publisher's id, another encoder's view is refused, declared references land on their source rows, and the view builds the same into R2");
788
+ console.log("view.js self-check ok — domain names and embedded text are stable, the schema is applied, an unmoved head reads nothing, additions ship as delta shards, removals compact the domain, an excluded repo is dropped and a restored one returns, a repo name pinned to one publisher drops the others, the real reader sees exactly the live records by the publisher's id, another encoder's view is refused, declared references land on their source rows, and the view builds the same into R2");
770
789
  }
@@ -37,7 +37,10 @@ fangorn push
37
37
  ```
38
38
 
39
39
  The view picks it up within 5 minutes at `<url>/view`. A source that crawls something
40
- on a schedule goes in `app.json` `sources`, and publishes with
40
+ on a schedule goes in `app.json` `sources`. Once there are sources, the view presents
41
+ only those repos, published by your wallet, plus the namespaces listed in `"live"`
42
+ (`"live": ["items"]` for any wallet's `items`, `"0x<wallet>/items"` for one wallet's).
43
+ A source publishes with
41
44
  `npx westmarch-ship app.json --no-deploy` (the push to `main` deploys; without the flag it
42
45
  would deploy a second copy over this Worker). See westmarch's docs.
43
46
 
@@ -6,7 +6,7 @@
6
6
  "deploy": "wrangler deploy"
7
7
  },
8
8
  "dependencies": {
9
- "@fangorn-network/westmarch": "0.1.1",
9
+ "@fangorn-network/westmarch": "0.1.2",
10
10
  "@huggingface/transformers": "^4.3.1",
11
11
  "wrangler": "^4.83.0"
12
12
  }