Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
* Added ability to suffer immense pain, trying to use a UI created/modified by pifreak!
* Added ability to upload list of SEEDS in TXT or CSV format!

## Assistant, WebMCP, and seed lists
* **Chat button (top right):** an in-app Balatro assistant powered by the Anthropic API. Paste your own API key in its settings (it stays in your browser's localStorage and goes straight to `api.anthropic.com`). It reads and drives the app through tools: analyze a seed, read any ante's shop queue and packs, find items across antes, mark purchases, navigate, load seed lists, edit the JAML filter, and `remember` notes about you so later sessions are personalized.
* **WebMCP:** the same tools are registered on `document.modelContext` (with the `@mcp-b/webmcp-polyfill` fallback), so browser agents and the Chrome DevTools WebMCP pane / Model Context Tool Inspector extension can call `analyze_seed`, `get_seed_overview`, `find_items`, `buy_item`, `load_seed_list`, `step_seed_queue`, `set_jaml_filter` and friends directly.
* **Seed lists:** paste hundreds or thousands of seeds (one per line, comma/space separated, or a CSV with seeds in the first column) into the *Seed list* box in the sidebar. `ArrowLeft` / `ArrowRight` step through them, `Home` / `End` jump to the first/last; neighbours are pre-analyzed so switching is instant.

## pifreak's roadmap:
* Continue testing motely-wasm
* Continue unlocking performance using SharedArrayBuffer properly?
Expand Down
62 changes: 62 additions & 0 deletions ___tests___/webmcp.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { describe, expect, it } from "vitest";
import { analyzeSeed } from "../src/modules/GameEngine";
import { describeAnte, findItems, summarizeSeed } from "../src/modules/webmcp/summary";
import { parseSeedList } from "../src/modules/state/store";

const options = { buys: {}, sells: {}, showCardSpoilers: false, unlocks: [], events: [], maxMiscCardSource: 15 };

function analyze(seed: string, antes = 3) {
const results = analyzeSeed({ seed, deck: "Ghost Deck", stake: "White Stake", gameVersion: "10106", antes, cardsPerAnte: 20 }, options);
if (!results) throw new Error("analysis failed");
return results;
}

describe("seed summaries", () => {
it("summarizes every generated ante in order", () => {
const summary = summarizeSeed("WEEJOKER", analyze("WEEJOKER", 4), 5);
expect(summary.antes.map((a) => a.ante)).toEqual([0, 1, 2, 3, 4]);
for (const ante of summary.antes) {
expect(ante.shop.length).toBeLessThanOrEqual(5);
expect(ante.shopSize).toBeGreaterThan(0);
expect(typeof ante.boss).toBe("string");
expect(ante.packs.length).toBeGreaterThan(0);
}
});

it("finds items where the ante details say they are", () => {
const results = analyze("WEEJOKER");
const ante1 = describeAnte(results.antes[1]);
const first = ante1.shop[0];
const bare = first.item.replace(/ \(.*\)$/, "");
const hits = findItems(results, [bare])[bare];
expect(hits.some((h) => h.ante === 1 && h.source === "shop" && h.index === 0)).toBe(true);
});

it("returns empty matches for unknown names", () => {
expect(findItems(analyze("WEEJOKER"), ["Definitely Not A Card"])).toEqual({ "Definitely Not A Card": [] });
});
});

describe("parseSeedList", () => {
it("accepts newline, comma and whitespace separated seeds and dedupes", () => {
expect(parseSeedList("aleeb\nPIROCKS weejoker\naleeb")).toEqual(["ALEEB", "PIROCKS", "WEEJOKER"]);
});

it("takes the first column of CSV rows, skips numeric tally columns and a seed header", () => {
expect(parseSeedList("seed,score\nFAJK8SAR, 11, 1, 0\n16661,2")).toEqual(["FAJK8SAR", "16661"]);
});

it("keeps every seed of a comma-separated list", () => {
expect(parseSeedList("ALEEB,PIROCKS, WEEJOKER;16661\tABC1")).toEqual(["ALEEB", "PIROCKS", "WEEJOKER", "ABC1"]);
});

it("includes ante 0 in item searches", () => {
const results = analyze("WEEJOKER", 2);
const name = describeAnte(results.antes[0]).shop[0].item.replace(/ \(.*\)$/, "");
expect(findItems(results, [name])[name].some((h) => h.ante === 0)).toBe(true);
});

it("sanitizes zeros and junk", () => {
expect(parseSeedList("a0b-c!d 123456789ABC")).toEqual(["AOBCD", "12345678"]);
});
});
120 changes: 120 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
"lint": "eslint src"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.125.0",
"@mantine/carousel": "8.3.14",
"@mantine/code-highlight": "8.3.14",
"@mantine/core": "8.3.14",
"@mantine/hooks": "8.3.14",
"@mantine/spotlight": "8.3.14",
"@mcp-b/webmcp-polyfill": "^5.1.0",
"@tabler/icons-react": "^3.41.1",
"@tanstack/react-query": "^5.85.9",
"embla-carousel-react": "^8.6.0",
Expand Down
7 changes: 7 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import { SeedOptionsProvider } from "./modules/state/optionsProvider.tsx";
import { DownloadSeedResultProvider } from "./modules/state/downloadProvider.tsx";
import { BlueprintThemeProvider, useBlueprintTheme } from "./modules/state/themeProvider.tsx";
import { JamlSearchProvider } from "./modules/state/jamlSearchContext.tsx";
import AssistantDrawer from "./components/AssistantDrawer.tsx";
import { SeedQueueHotkeys } from "./components/SeedQueue.tsx";
import { startWebMcp } from "./modules/webmcp/register.ts";

startWebMcp();

const queryClient = new QueryClient()

Expand Down Expand Up @@ -42,6 +47,8 @@ export default function App() {
<JamlSearchProvider>
<ProviderContainer>
<Blueprint/>
<AssistantDrawer/>
<SeedQueueHotkeys/>
</ProviderContainer>
</JamlSearchProvider>
</BlueprintThemeProvider>
Expand Down
Loading
Loading