diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ce1b8718..31260e62 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -40,7 +40,7 @@ // Open in GitHub: provides links to open the current file + line number in GitHub. "ziyasal.vscode-open-in-github", // Protocol buffer support. - "zxh404.vscode-proto3", + "drblury.protobuf-vsc", // Spell checker. "streetsidesoftware.code-spell-checker", // Add autotmatic line/comment wrapper. diff --git a/.devcontainer/git_config.sh b/.devcontainer/git_config.sh index 52a4b599..a045077a 100755 --- a/.devcontainer/git_config.sh +++ b/.devcontainer/git_config.sh @@ -30,12 +30,6 @@ git config --global submodule.recurse true # https://stackoverflow.com/questions/27417656/should-diff3-be-default-conflictstyle-on-git git config --global merge.conflictstyle diff3 -# When force-pushing (e.g. a rebased branch), check that the upstream -# branch is still exactly the way it was when it was last fetched. This -# prevents force-pushes to the same branch from two different machines -# (or developers) from accidentally wiping out the first pushed changes. -git config --global push.forceWithLease true - # Do some extra work to pre-configure GitHub authentication when running # in a codespace. Skip this for a local devcontainer. if [[ "${CODESPACES:-}" == "true" ]]; then diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 703d0cd1..da9276b1 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -129,10 +129,15 @@ jobs: QUERY="$QUERY except attr(\"tags\",\"flaky\",//tests/...)" fi - # 4) Run the query and pipe into bazel test. + # 4) Build test targets first so that test execution + # doesn't compete with compilation for I/O. echo "▶ bazel query: $QUERY" - bazel query "$QUERY" \ - | xargs bazel test + TARGETS=$(bazel query "$QUERY") + echo "$TARGETS" | xargs bazel build + + # 5) Run tests; all build artifacts are cached so + # this only executes tests. + echo "$TARGETS" | xargs bazel test build-reboot-documentation: name: Build Reboot documentation @@ -211,7 +216,12 @@ jobs: QUERY="$QUERY except attr(\"tags\",\"flaky\",//tests/...)" fi - # 4) Run the query and pipe into bazel test. + # 4) Build test targets first so that test execution + # doesn't compete with compilation for I/O. echo "▶ bazel query: $QUERY" - bazel query "$QUERY" \ - | xargs bazel test + TARGETS=$(bazel query "$QUERY") + echo "$TARGETS" | xargs bazel build + + # 5) Run tests; all build artifacts are cached so + # this only executes tests. + echo "$TARGETS" | xargs bazel test diff --git a/bazel/py_test_with_workspace_dir.bzl b/bazel/py_test_with_workspace_dir.bzl index 21385fb3..07f7c384 100644 --- a/bazel/py_test_with_workspace_dir.bzl +++ b/bazel/py_test_with_workspace_dir.bzl @@ -60,8 +60,9 @@ def py_test_with_workspace_dir( visibility = visibility, main = main, tags = tags + [ - # Force test to be executed unconditionally. Cached results may be - # old since this test tests more than what's in verstions_test.py. + # Force test to be executed unconditionally. Cached results + # may be old since this test tests things on the filesystem + # that aren't expressable as a Bazel dependency. "external", ], ) diff --git a/charts/reboot/Chart.yaml b/charts/reboot/Chart.yaml index bb4a80e9..9b949608 100644 --- a/charts/reboot/Chart.yaml +++ b/charts/reboot/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: 3.3.2 name: reboot -version: "0.46.0" +version: "1.0.3" description: Reboot is a programming framework that enables transactional microservices built with the developer in mind. type: application keywords: @@ -10,4 +10,4 @@ keywords: - scalable - reactive home: https://docs.reboot.dev/ -appVersion: "0.46.0" +appVersion: "1.0.3" diff --git a/ci/templates/build_and_test.yml.j2 b/ci/templates/build_and_test.yml.j2 index 28603c24..4610544e 100644 --- a/ci/templates/build_and_test.yml.j2 +++ b/ci/templates/build_and_test.yml.j2 @@ -175,10 +175,15 @@ jobs: QUERY="$QUERY except attr(\"tags\",\"flaky\",//tests/...)" fi - # 4) Run the query and pipe into bazel test. + # 4) Build test targets first so that test execution + # doesn't compete with compilation for I/O. echo "▶ bazel query: $QUERY" - bazel query "$QUERY" \ - | xargs bazel test + TARGETS=$(bazel query "$QUERY") + echo "$TARGETS" | xargs bazel build + + # 5) Run tests; all build artifacts are cached so + # this only executes tests. + echo "$TARGETS" | xargs bazel test build-reboot-documentation: name: Build Reboot documentation @@ -281,7 +286,12 @@ jobs: QUERY="$QUERY except attr(\"tags\",\"flaky\",//tests/...)" fi - # 4) Run the query and pipe into bazel test. + # 4) Build test targets first so that test execution + # doesn't compete with compilation for I/O. echo "▶ bazel query: $QUERY" - bazel query "$QUERY" \ - | xargs bazel test + TARGETS=$(bazel query "$QUERY") + echo "$TARGETS" | xargs bazel build + + # 5) Run tests; all build artifacts are cached so + # this only executes tests. + echo "$TARGETS" | xargs bazel test diff --git a/documentation/docs/ai_chat_apps/examples.mdx b/documentation/docs/ai_chat_apps/examples.mdx index dc46a7d0..776e46ce 100644 --- a/documentation/docs/ai_chat_apps/examples.mdx +++ b/documentation/docs/ai_chat_apps/examples.mdx @@ -1,5 +1,59 @@ # AI Chat App Examples +## `agent-wiki` + +[`reboot-dev/reboot-agent-wiki`](https://github.com/reboot-dev/reboot-agent-wiki) - _Python backend, React AI Chat App UIs_ + +The `agent-wiki` example is a shared knowledge base humans +and AIs can both read from and write to. Users hand in raw +conversation transcripts; a background "librarian" agent +(built with Pydantic AI) progressively distills those +transcripts into a small, well-organized set of markdown +pages linked from the wiki's own markdown body, which +serves as a living table of contents. Humans browse the +result in embedded React UIs. + +It demonstrates: + +* `UI` methods that render React views (wiki, page, + transcript) inside the AI chat interface. +* A long-running `workflow` method (`Wiki.ingest`) that + acts as a per-wiki background agent, reacting to new + transcripts as they arrive. +* `Transaction`s that atomically create related state + across multiple types (e.g. `User.create_wiki` creates a + `Wiki` and records its ID on the user in one step). +* Cross-state references via `:` URIs + embedded in markdown, letting the agent build a graph of + pages without a dedicated link table. +* An in-process test suite that exercises the librarian + workflow with a scripted Pydantic AI `FunctionModel`, so + no real Anthropic calls are needed in CI. + +## `chick-potle` + +[`reboot-dev/reboot-chick-potle`](https://github.com/reboot-dev/reboot-chick-potle) - _Python backend, React AI Chat App UIs_ + +The `chick-potle` example is a small food-ordering AI Chat +App. The AI calls tools to start an order, browse the menu, +and add or remove items from the cart; humans see two +embedded React UIs — a menu grid and a cart — rendered +alongside the conversation. + +It demonstrates: + +* `UI` methods that render React views (menu, cart) inside + the AI chat interface. +* The `User` type: auto-constructed per authenticated user, + acting as an entry point that creates a `FoodOrder` via a + `Transaction` (`User.start_order`). +* MCP `Tool`s (`get_menu`, `get_cart`, `add_to_cart`, + `remove_from_cart`) that let the AI drive the order + programmatically. +* Generated React hooks (`useFoodOrder()`) shared between + the menu and cart UIs, so adding an item from one view + immediately updates the other. + ## `ai-chat-counter` _Python backend, React AI Chat App UIs_ diff --git a/documentation/docs/ai_chat_apps/get_started.mdx b/documentation/docs/ai_chat_apps/get_started.mdx index 64bfb668..8f2e73ea 100644 --- a/documentation/docs/ai_chat_apps/get_started.mdx +++ b/documentation/docs/ai_chat_apps/get_started.mdx @@ -68,12 +68,11 @@ Naming a type \`User\` in your \`API(...)\` is special: - During development all users of your app are automatically "authenticated" with the built-in \`Anonymous\` OAuth provider, so every user of your app gets a \`User\` instance. -- All methods on \`User\` are automatically callable by the AI. - \`User\` acts as an entry point: its methods create other state types (like \`Counter\`) whose IDs the AI tracks in its context window. -- Methods on non-\`User\` types use \`mcp=Tool()\` to be - AI-callable. +- Use \`mcp=Tool()\` on every method you want the AI to call, + including \`User\` methods. Use \`mcp=None\` to hide a method. `}> @@ -147,6 +146,7 @@ api = API( "the `counter_id`, which is not " "human-readable but should be passed to " "future tool calls that need it.", + mcp=Tool(), ), list_counters=Reader( request=None, @@ -156,6 +156,7 @@ api = API( "description for each. The `counter_id` " "is not human-readable, but use it when " "calling tools that take a `counter_id`.", + mcp=Tool(), ), ), ), @@ -173,6 +174,7 @@ api = API( request=CreateCounterRequest, response=None, factory=True, + mcp=None, ), get=Reader( request=None, @@ -191,6 +193,7 @@ api = API( description=Reader( request=None, response=DescriptionResponse, + mcp=None, ), ), ), @@ -241,6 +244,7 @@ and optionally a \`request\`. You access state via ```python # backend/src/servicers/counter.py from ai_chat_counter.v1.counter import ( + CounterEntry, CreateCounterRequest, CreateCounterResponse, ListCountersResponse, @@ -280,7 +284,7 @@ class UserServicer(User.Servicer): for counter_id in self.state.counter_ids: response = await Counter.ref(counter_id).description(context) counters.append( - User.CounterEntry( + CounterEntry( counter_id=counter_id, description=response.description, ) @@ -699,7 +703,7 @@ dev run --watch=web/dist/**/*.html dev run --python # Save state between restarts. -dev run --name=ai-chat-counter +dev run --application-name=ai-chat-counter # Entrypoint. dev run --application=backend/src/main.py @@ -760,9 +764,8 @@ touch mcp_servers.json { "mcpServers": { "counter-server": { - "type": "streamable-http", "url": "http://localhost:9991/mcp", - "auth": "oauth" + "useOAuth": true } } } @@ -771,7 +774,7 @@ touch mcp_servers.json ```sh -npx @mcpjam/inspector@v2.0.4 --config mcp_servers.json --server counter-server +npx @mcpjam/inspector@2.4.0 --config mcp_servers.json --server counter-server ``` diff --git a/documentation/docs/ai_chat_apps/get_started_claude_code.mdx b/documentation/docs/ai_chat_apps/get_started_claude_code.mdx index f6952b23..4b23faf0 100644 --- a/documentation/docs/ai_chat_apps/get_started_claude_code.mdx +++ b/documentation/docs/ai_chat_apps/get_started_claude_code.mdx @@ -107,7 +107,7 @@ After the skill finishes building, it starts the app with [MCPJam Inspector](https://mcpjam.com): ```sh -npx @mcpjam/inspector@v2.0.4 \ +npx @mcpjam/inspector@2.4.0 \ --config mcp_servers.json --server my-app ``` diff --git a/documentation/docs/ai_chat_apps/what_is.mdx b/documentation/docs/ai_chat_apps/what_is.mdx index d1f0cf4d..63466d5d 100644 --- a/documentation/docs/ai_chat_apps/what_is.mdx +++ b/documentation/docs/ai_chat_apps/what_is.mdx @@ -112,6 +112,7 @@ api = API( "the `counter_id`, which is not " "human-readable but should be passed to " "future tool calls that need it.", + mcp=Tool(), ), list_counters=Reader( request=None, @@ -121,6 +122,7 @@ api = API( "description for each. The `counter_id` " "is not human-readable, but use it when " "calling tools that take a `counter_id`.", + mcp=Tool(), ), ), ), @@ -138,6 +140,7 @@ api = API( request=CreateCounterRequest, response=None, factory=True, + mcp=None, ), get=Reader( request=None, @@ -156,6 +159,7 @@ api = API( description=Reader( request=None, response=DescriptionResponse, + mcp=None, ), ), ), @@ -164,10 +168,10 @@ api = API( -All methods on `User` are automatically callable by the AI. -Methods on other types (like `Counter`) are exposed with -`mcp=Tool()`. The AI receives the state ID when it creates -a new instance and uses it in subsequent calls. +Use `mcp=Tool()` on every method you want the AI to call, +including `User` methods. Methods on other types (like `Counter`) +that expose `mcp=Tool()` receive a state ID from the AI when it +creates a new instance and uses it in subsequent calls. Under the hood, AI Chat Apps use [MCP (the Model Context Protocol)](/learn_more/mcp_apps) — the standard that AI clients use to diff --git a/documentation/docs/deploy_on_reboot_cloud.md b/documentation/docs/deploy_on_reboot_cloud.md index 4247b35d..89917eda 100644 --- a/documentation/docs/deploy_on_reboot_cloud.md +++ b/documentation/docs/deploy_on_reboot_cloud.md @@ -5,7 +5,7 @@ To deploy your Reboot app to production, you can use the [Reboot Cloud](https://cloud.reboot.dev/). The Reboot Cloud leverages Reboot's safety guarantees to automatically partition and deploy your application across a cluster of machines, providing automatic scaling and high availability! ```console -$ rbt cloud up --image-name=... +$ rbt cloud up --name=my-app --organization=my-org ... Application starting; your application will be available at: @@ -25,11 +25,4 @@ of Reboot Cloud on your own hardware. ## Comparison -| | `rbt serve` (EBS or equivalent)| `rbt serve` (EFS or equivalent)| Reboot Cloud | Reboot Cloud Enterprise | -| :-------------------------------- | :----------------------------- | :----------------------------- | :---------------------------------------------- | :---------------------------------------- | -| Physical backups | yes | yes | yes | yes | -| Replication | within an availability zone | within a region | within a region | within a region | -| High availability (failover time) | \~minutes | \~seconds | \~seconds | \~seconds | -| Vertical scaling | yes | yes | yes | yes | -| Horizontal scaling | no | no | yes | yes | -| Availability | available now | available now | [Join the waitlist](https://cloud.reboot.dev/)! | [Contact Reboot](mailto:team@reboot.dev)! | +For a side-by-side of Reboot Cloud against `rbt serve` on EBS and EFS, see [the deployment comparison on "Deploy on your own"](/deploy_on_your_own#comparison). diff --git a/documentation/docs/develop_locally.md b/documentation/docs/develop_locally.md index e1c47594..fe5a20ab 100644 --- a/documentation/docs/develop_locally.md +++ b/documentation/docs/develop_locally.md @@ -53,7 +53,7 @@ based on your selections. To set up a Python backend, use the following command: ```shell -rbt init --name=reboot_hello_world --backend=python +rbt init --application-name=reboot_hello_world --backend=python ``` This will generate the necessary source files and include a Python script to @@ -64,7 +64,7 @@ test the connection to the backend. For a TypeScript backend, use the following command: ```shell -rbt init --name=reboot_hello_world --backend=nodejs +rbt init --application-name=reboot_hello_world --backend=nodejs ``` This will create the source files and configure package.json if it doesn't @@ -75,7 +75,7 @@ already exist. To initialize a frontend with React, use the following command: ```shell -rbt init --name=reboot_hello_world --frontend=react +rbt init --application-name=reboot_hello_world --frontend=react ``` This command generates the source files and sets up package.json in the web @@ -87,12 +87,12 @@ for Python and TypeScript: For Python: ```shell -rbt init --name=reboot_hello_world --backend=python --frontend=react +rbt init --application-name=reboot_hello_world --backend=python --frontend=react ``` For TypeScript: ```shell -npx rbt init --name=reboot_hello_world --backend=nodejs --frontend=react +npx rbt init --application-name=reboot_hello_world --backend=nodejs --frontend=react ``` ::: --> @@ -131,13 +131,13 @@ dev run --watch=backend/**/*.py dev run --python # Save state between chaos restarts. -dev run --name=bank +dev run --application-name=bank # Run the application! dev run --application=backend/src/main.py # When expunging, expunge that state we've saved. -dev expunge --name=bank +dev expunge --application-name=bank ``` @@ -150,7 +150,7 @@ dev expunge --name=bank ```shell # Set the name of our application. -dev run --name=counter +dev run --application-name=counter # Declare that this is a nodejs application. dev run --nodejs @@ -286,7 +286,7 @@ changed the default port from `9991`). ### Persisting state during development By default your application state will not be persisted across -restarts. To persist state, you can pass a `--name=...` flag to `rbt dev +restarts. To persist state, you can pass an `--application-name=...` flag to `rbt dev run`. When state is persisted, backwards incompatible changes in your `.proto` files @@ -304,5 +304,5 @@ During early development of your application, either `ask` or `expunge` are reasonable choices. Once you have deployed to production, the `fail` option can encourage more caution while adjusting schemas. -You can also use `rbt dev expunge --name=...` to manually expunge data (where `--name=...` -is the same value you're using for `rbt dev run`). +You can also use `rbt dev expunge --application-name=...` to manually expunge data (where +`--application-name=...` is the same value you're using for `rbt dev run`). diff --git a/documentation/docs/full_stack_apps/python.mdx b/documentation/docs/full_stack_apps/python.mdx index a8c1a174..9b79ad53 100644 --- a/documentation/docs/full_stack_apps/python.mdx +++ b/documentation/docs/full_stack_apps/python.mdx @@ -306,14 +306,14 @@ First, tell Reboot where the proto files live, the \`api/\` directory, and where to output the generated code, \`backend/api/\`. Next, tell Reboot how to run your app. Give the app a name via -\`--name=chat-room\` to persist state between app restarts. Watch for source -code changes via \`--watch=...\` to restart the app whenever there is a -modification. Finally, tell Reboot that it should use Python to run +\`--application-name=chat-room\` to persist state between app restarts. Watch +for source code changes via \`--watch=...\` to restart the app whenever there +is a modification. Finally, tell Reboot that it should use Python to run our app. :::tip If you want to remove all of your changes between app restarts, do not pass the -\`--name\` flag to \`rbt\`. +\`--application-name\` flag to \`rbt\`. ::: Last, specify the entrypoint for your app. @@ -326,7 +326,7 @@ See a complete list of supported flags by running \`rbt dev run --help\`. generate api/ generate --python=backend/api/ -dev run --name=chat-room +dev run --application-name=chat-room dev run --watch=backend/src/**/*.py dev run --python dev run --application=backend/src/main.py diff --git a/documentation/docs/full_stack_apps/typescript.mdx b/documentation/docs/full_stack_apps/typescript.mdx index 7bced6bd..6be735ee 100644 --- a/documentation/docs/full_stack_apps/typescript.mdx +++ b/documentation/docs/full_stack_apps/typescript.mdx @@ -318,13 +318,12 @@ First, tell Reboot where the Zod schemas live, the \`api/\` directory, and where to output the generated code, also in \`api/\`. Next, tell Reboot how to run your app. Give the app a name via -\`--name=chat-room\` to persist state between app restarts. Finally, tell Reboot -that it should use Node.js to run -our app. +\`--application-name=chat-room\` to persist state between app restarts. Finally, +tell Reboot that it should use Node.js to run our app. :::tip If you want to remove all of your changes between app restarts, do not pass the -\`--name\` flag to \`rbt\`. +\`--application-name\` flag to \`rbt\`. ::: Last, specify the entrypoint for your app. @@ -338,7 +337,7 @@ See a complete list of supported flags by running \`rbt dev run --help\`. generate api/ generate --nodejs=api/ -dev run --name=chat-room +dev run --application-name=chat-room dev run --watch=backend/src/**/*.ts dev run --nodejs dev run --application=backend/src/main.ts diff --git a/documentation/docs/learn_more/agents.mdx b/documentation/docs/learn_more/agents.mdx new file mode 100644 index 00000000..3d34f3e1 --- /dev/null +++ b/documentation/docs/learn_more/agents.mdx @@ -0,0 +1,518 @@ +# Agents + +Reboot's `reboot.agents` package lets you run [Pydantic AI](https://ai.pydantic.dev/) +agents inside Reboot workflows with **durable, replay-safe execution +of model calls and tool calls** -- you get back the same answer on +re-runs without re-hitting the LLM provider, and tool side-effects +that have already completed are not repeated. + +This page covers what's specific to running a Pydantic AI agent on +Reboot. For everything that isn't Reboot-specific (model providers, +tool argument schemas, output types, etc.), see the +[Pydantic AI documentation](https://ai.pydantic.dev/). + +## What you get + +When you wrap a Pydantic AI agent with Reboot's `Agent`: + +- **Every model call (`request` / `request_stream`) is memoized** + via `at_least_once`. On workflow replay the stored `ModelResponse` + is returned instead of calling the provider again. +- **Every tool call** -- whether registered via `@agent.tool`, passed + as `tools=`, or contributed by a `toolsets=` -- is memoized too, + so a tool's side effects aren't repeated on replay. During [effect + validation](/learn_more/side_effects) tools DO re-run, which helps + surface non-determinism early. +- **Streaming runs are drained inside a memoized block.** The + `StreamedRunResult` you get back is backed by a fully-stored + response; iteration replays the stored events instead of + re-streaming from the provider. Live token-by-token streaming is + fundamentally incompatible with replay. +- **Configuration drift is surfaced.** A snapshot of the agent's + static config + per-call kwargs is taken at the start of each run. + On replay, anything that has changed (instructions, system prompt, + model, tools, etc.) produces a targeted log warning so you know + that previously memoized responses may not reflect the current + configuration. + +## Constructing an agent + +There are two ways to construct a Reboot `Agent`: + +### Directly + +```python +from reboot.agents.pydantic_ai import Agent + +agent = Agent( + "anthropic:claude-sonnet-4-5", + name="research_agent", + system_prompt="You are a careful research assistant.", +) +``` + +The constructor accepts the same arguments as +[`pydantic_ai.Agent`](https://ai.pydantic.dev/api/agent/#pydantic_ai.Agent.__init__). + +### Wrapping an existing `pydantic_ai.Agent` + +```python +import pydantic_ai +from reboot.agents.pydantic_ai import Agent + +bare = pydantic_ai.Agent("anthropic:claude-sonnet-4-5", name="research_agent") +agent = Agent.wrap(bare) +``` + +Useful when the agent comes from code you don't control (e.g., a +factory function or third-party library). Wrapping doesn't mutate +`bare`; the original agent stays usable on its own. + +A `name=` is required either way -- Reboot uses the agent's name as +part of the memoization key for every model and tool call. The name +is locked at construction; assigning to `agent.name` raises a +`UserError`. + +## Running the agent + +Reboot mirrors the four `pydantic_ai` entry points, with one extra +required argument: the **`WorkflowContext`** for the surrounding +workflow. + +```python +@classmethod +async def workflow( + cls, + context: WorkflowContext, + request: ResearchRequest, +) -> ResearchResponse: + result = await agent.run(context, request.query) + return ResearchResponse(answer=result.output) +``` + +Available methods: + +- `agent.run(context, user_prompt, ...)` +- `async with agent.iter(context, ...) as run: ...` +- `async with agent.run_stream(context, ...) as result: ...` +- `async for event in agent.run_stream_events(context, ...): ...` + +The synchronous variants `run_sync` and `run_stream_sync` are not +supported -- a Reboot workflow method is always async. + +### Use `variant=` to differentiate same-args calls + +Reboot refuses duplicate calls with the same `(name, user_prompt, +variant, message_history)` in the same workflow / control-loop +iteration -- this catches the common bug of accidentally invoking the +same agent twice and silently sharing a memoized response. To +deliberately make multiple calls with the same prompt (e.g., asking +the same question N times for diversity, or after changes in the +environment or with your deps that might affect tool responses), pass +distinct `variant=` strings: + +```python +results = await asyncio.gather(*[ + agent.run(context, "Generate an idea", variant=f"draft-{i}") + for i in range(5) +]) +``` + +## Tools + +Reboot's `@agent.tool_plain` and `@agent.tool` mirror `pydantic_ai`'s +decorators with one additional convenience: `@agent.tool` accepts a +`WorkflowContext` as the first parameter, BEFORE the `pydantic_ai` +`RunContext`. This lets your tool body call into other Reboot data +types easily, and you can also wrap external work in `at_least_once` +although know that every tool call is already wrapped in an +`at_least_once` for you. + +```python +import uuid +from pydantic_ai import RunContext +from reboot.agents.pydantic_ai import Agent +from reboot.aio.contexts import WorkflowContext +from reboot.aio.workflows import at_least_once + +agent = Agent("anthropic:claude-sonnet-4-5", name="research_agent") + +@agent.tool_plain +def now() -> str: + """Return the current UTC timestamp.""" + # Because every tool is wrapped in an `at_least_once` + # the same UTC timestamp will be returned during replay! + return datetime.utcnow().isoformat() + +@agent.tool +async def stripe_create_invoice( + context: WorkflowContext, + run: RunContext[None], + customer_id: str, + ... +) -> list[str]: + """Ask Stripe to create an invoice using an idempotency key so + only one invoice is ever created even in the event of retry.""" + + async def make_idempotency_key() -> uuid.UUID: + return uuid.uuid4() + + # Using `at_least_once` is not strictly necessary, + # but can be instrumental memoize values that you + # want to ensure will be the same if this tool call + idempotency_key = await at_least_once( + "Make idempotency key for Stripe", + context, + make_idempotency_key, + ) + + # Now call Stripe. + ... +``` + +:::tip +The above example was merely for demonstration purposes and in +practice you should use Reboot's built-in helper for creating +deterministic idempotency keys with a `WorkflowContext`: +`context.make_idempotency_key(alias="Make idempotency key for +Stripe")` +::: + +`@agent.tool` and `@agent.tool_plain` work both at construction +time and after `Agent.wrap(...)`. Tools you pass as `tools=` to the +constructor or via `toolsets=` are also wrapped automatically -- you +don't need to do anything special to opt them in to memoization. + +For everything else about tool definitions (argument validation, +docstring parsing, output schemas, etc.), see +[`pydantic_ai`'s tools docs](https://ai.pydantic.dev/tools/). + +### MCP servers + +[MCP](https://ai.pydantic.dev/mcp/) toolsets work transparently +under Reboot. Both `pydantic_ai.mcp.MCPServer` and +`pydantic_ai.toolsets.fastmcp.FastMCPToolset` are detected +automatically when you pass them via `toolsets=` (at construction +time or per-run) and wrapped to work with Reboot. + +`MCPServer.cache_tools=True` (the default) is honored as well: the +first `get_tools` of an agent run hits the MCP server, subsequent +calls within the same run reuse the cached `ToolDefinition`s. Set +`cache_tools=False` if your MCP server's tool list may change +mid-workflow. (`FastMCPToolset` has no `cache_tools` toggle and isn't +cached by Reboot, matching `pydantic_ai`'s contract.) + +If you have **more than one MCP toolset on the same agent**, all +but at most one must have a non-`None` `id=`: + +```python +from pydantic_ai.mcp import MCPServerStdio +from reboot.agents.pydantic_ai import Agent + +# OK -- distinct ids: +weather = MCPServerStdio("weather-mcp", id="weather") +calendar = MCPServerStdio("calendar-mcp", id="calendar") + +agent = Agent( + "anthropic:claude-sonnet-4-5", + name="planner", + toolsets=[weather, calendar], +) +``` + +Reboot uses each MCP toolset's `id` to disambiguate memoization keys +for `get_tools` / `get_instructions` / `call_tool` across replays. Two +id-less servers would silently clobber each other's results, so we +raise a `UserError` at agent construction time when this is +detected. (For `MCPServer`, `tool_prefix=...` doubles as the id when +`id=` isn't set.) + +### Why your tool doesn't re-run on replay (but should still be idempotent) + +Reboot wraps every tool call in `at_least_once`. On replay, the stored +return value is reused without invoking your tool function. Your tool +function therefore should not be relied upon to be "called once per +replay". Note that during [effect +validation](/learn_more/side_effects), Reboot re-runs your tool to +verify your code is deterministic, so if you are performing external +side-effects inside your tool body you need to ensure they are done +idempotently (or consider using an `at_most_once` block instead, but +only after considering how that handles failures). + +:::tip + +Effect validation helps you write safe code during development instead +of waiting for something to go wrong in production! The reality is +that even with durable execution, if you've _started_ executing some +code but haven't completed it, then on replay you'll have to re-run +that code again. Effect validation helps uncover those places where +you didn't realize you had idempotency issues! + +::: + +## Streaming events with `event_stream_handler` + +Pydantic AI's +[`event_stream_handler`](https://ai.pydantic.dev/agents/#event-stream-handler) +is a callback invoked for each event during a streaming run. +Reboot doesn't wrap this callback specially -- it's just async +Python code that runs inside your workflow body, so the standard +"wrap side-effects with `at_least_once`" guidance applies. + +Define your handler **inside the workflow method** (or as a helper +that takes `context` as a parameter and returns the closure) so it +can capture the surrounding `WorkflowContext`: + +```python +@classmethod +async def workflow( + cls, + context: WorkflowContext, + request: ChatRequest, +) -> ChatResponse: + + async def handler( + run: RunContext[None], + stream, + ) -> None: + i = 0 + async for event in stream: + # Push each event to the UI via a durable side-effect + # so the post is only sent once across replays. + await at_least_once( + f"Post event '{i}' to UI", + context, + lambda: post_to_chat_ui(event), + ) + i += 1 + + result = await agent.run( + context, + request.message, + event_stream_handler=handler, + ) + return ChatResponse(answer=result.output) +``` + +Note: streaming is drained-and-stored, so iteration is replay-safe. +The events your handler sees on replay are identical to the events +on first run. + +## Parallel tool execution mode + +Pydantic AI's +[`parallel_tool_call_execution_mode`](https://ai.pydantic.dev/api/agent/#pydantic_ai.agent.AbstractAgent.parallel_tool_call_execution_mode) +controls how concurrent tool calls dispatched in a single turn +are scheduled. It accepts three values upstream: +`'sequential'`, `'parallel'`, and `'parallel_ordered_events'`. +Reboot accepts the first and last and rejects `'parallel'` at +construction time. + +```python +agent = Agent( + "anthropic:claude-sonnet-4-5", + name="research_agent", + parallel_execution_mode="parallel_ordered_events", # default +) +``` + +`'parallel'` is not a valid execution mode because it yields +tool-result events in *completion* order, which depends on asyncio +scheduling which is not reliable on replay. + +The two replay-safe modes: + +- `'parallel_ordered_events'` (default): tools dispatch + concurrently (`asyncio.gather`-style), but events are emitted + in the original call order once everything has completed. +- `'sequential'`: one tool runs at a time, in original order. + Slower, but trivially deterministic. + +If the agent's tool functions are inherently order-sensitive +(rare for well-designed tools), prefer `'sequential'`. For +everything else `'parallel_ordered_events'` is what you want. + +## Overrides + +Pydantic AI's +[`Agent.override(...)`](https://ai.pydantic.dev/api/agent/#pydantic_ai.Agent.override) +context manager works on Reboot agents: + +```python +with agent.override(model="openai:gpt-4o-mini"): + result = await agent.run(context, "Quick check.") +``` + +`model=` is automatically wrapped so the override stays memoized +on replay. You may pass a `pydantic_ai.models.Model` instance, a +`KnownModelName` string, or any provider:model string -- all paths +are wrapped internally. + +`override(toolsets=...)` is **not currently supported** on a +Reboot `Agent`: the run-time toolset wrapping that memoizes tool +calls would shadow the override, and your toolsets would be +silently dropped. Pass toolsets at construction (`Agent(toolsets=)`) +or per-call (`agent.run(toolsets=)`) instead, both of which are +fully supported. If your use case requires this, please reach out +to the Reboot maintainers. + +## Configuration-drift warnings + +When you re-run a workflow that contains an agent run -- whether on +replay, retry, or effect validation -- Reboot compares a snapshot +of the agent's configuration to the one taken at the original run. +Any divergence emits a targeted `WARNING` log entry naming what +changed, e.g.: + +> `Agent 'research_agent': configured model 'anthropic:claude-sonnet-4-5' differs from the model 'openai:gpt-4o' used when this agent run was first executed; previously memoized model responses came from a different model and may not reflect what the current model would produce.` + +The fields tracked include the agent's static `instructions` and +`system_prompt`, the configured model, and the per-call kwargs +passed to `run/iter/run_stream/run_stream_events` (`instructions`, +`toolsets`, `builtin_tools`, `model_settings`, `output_type`, +`deferred_tool_results`). + +These are warnings, not errors -- they don't prevent the workflow +from completing. They do flag that the stored responses may not +match what the current configuration would produce, so you can +decide whether to invalidate the stored run (e.g., by bumping +`variant=`) or to accept the stale responses. + +## When is a call considered a "new" agent run? + +Within a single workflow / control-loop iteration, Reboot +identifies an agent run by the tuple `(agent_name, +user_prompt, variant, message_history)`. Two calls that match +on all four are the *same* run -- the second is rejected as a +duplicate (see [Use `variant=` to differentiate same-args +calls](#use-variant-to-differentiate-same-args-calls)). + +Anything that changes any of those four counts as a new run +and gets its own memoization slot: + +- A different `user_prompt` -- a new question is a new run. +- A different `message_history=` -- continuing a conversation + with new prior turns is a new run. +- A different `variant=` -- the explicit way to ask for a + fresh run when the prompt itself is unchanged. + +Per-call kwargs that affect *how* the model is invoked but +not *what* run it is -- `model=`, `instructions=`, +`toolsets=`, `model_settings=`, `output_type=`, `deps=`, +`builtin_tools=`, `deferred_tool_results=` -- do **not** make +the call a new run. Changing them on a replay is allowed, +but the [configuration-drift +warnings](#configuration-drift-warnings) will fire +if any of the snapshotted ones differ from the values used +when the run was first executed. + +## Watch out for compounded retries + +Several layers retry on failure, and combining them naively can +cause an order of magnitude more attempts than you intended: + +- **Reboot workflows** retry the entire method body when an + uncaught exception escapes (configurable via the workflow's + retry policy). +- **Reboot's `at_least_once`** retries the wrapped block until it + succeeds. +- **Pydantic AI's agent** has a `retries=` parameter (and a + `tool.retries=` per tool) that retries on tool / output + validation failures within a single agent run. +- **The model provider's HTTP client** (Anthropic SDK, OpenAI SDK, + etc.) typically retries 5xx / 429 / network errors on its own, + often with exponential backoff. + +For most setups you want to set the agent / tool `retries=` to a +small number (or 0) and let the workflow-level retry handle +hard failures. Provider HTTP retries can be left in place, but +when you're already wrapping with `at_least_once` on top, a +short retry budget on the provider client usually performs better +than a long one. + +## Tool returns and model responses must be picklable + +Reboot's `at_least_once` memoizes return values via `pickle`. That +means anything your tool function returns AND the `ModelResponse` +objects `pydantic_ai` produces must round-trip through pickle +cleanly: + +- Standard `pydantic_ai` message / response types are dataclasses + built for pickling — these work out of the box. +- Custom return values from your tools must also be + pickle-friendly. Open file handles, DB connections, and lambdas + are common offenders. Return plain data (strings, dicts, + pydantic models, dataclasses) instead. + +Reboot's snapshot digests fall back to `repr(...)` when pickle +fails, so the per-run config-drift warnings stay functional even +for unpicklable values; only the actual memoization path requires +pickle. + +## Observability (Logfire) + +Pydantic AI's [Logfire instrumentation](https://ai.pydantic.dev/logfire/) +works under Reboot. Set up Logfire as you normally would, and +agent runs / model calls / tool calls will produce spans that +show prompts, tools, and outputs alongside Reboot's own workflow +spans. Reboot's `at_least_once` wrapping is transparent to the +instrumentation — you'll see one outer agent span with the model +and tool calls nested inside, the same as a non-durable +`pydantic_ai` run. + +## Renaming an agent invalidates replay + +The agent's `name=` is part of the memoization key for every +model and tool call inside its runs. **If you rename an existing +agent in your code, all previously memoized runs become +unreachable** — workflows that depended on a stored response from +the old name will re-call the provider. + +This is a one-time deployment-day concern, not a runtime issue. +Plan agent name changes alongside any data migration / pinning +work the way you would any other shape change to your workflow's +durable state. + +## What's not memoized + +A few things deliberately fall outside Reboot's wrapping: + +- **Pydantic AI builtin tools** (web search, code execution, + etc.) -- these are executed by the model provider, and their + call/return parts are baked into the `ModelResponse` we already + memoize. They're durable for free. +- **Dynamic instructions / dynamic system prompt callables** -- + those are `pydantic_ai`'s `_system_prompt_dynamic_functions` and + the callable entries inside `instructions`. They run on every + turn and are not snapshotted. If your dynamic callables produce + different text between runs, wrap their non-deterministic work + in `at_least_once` yourself. +- **Tool side-effects you didn't wrap.** Reboot's wrapping + memoizes the *return* of your tool function, but if your tool + body performs un-memoized writes (e.g., a direct database + update), effect validation will surface the duplication. Wrap + external side-effects inside the tool body in `at_least_once` + -- see the `search` example above. + +## Limitations + +- `run_sync` and `run_stream_sync` are not supported (workflow + methods are async). +- Nested agent runs are not supported -- starting a second agent + run inside a tool function or another running agent raises a + `UserError`. To run multiple agents in sequence, run them from + the workflow body, one after the other. +- `override(toolsets=...)` is not supported (see above). +- `pydantic_ai`'s `parallel_tool_call_execution_mode='parallel'` + is rejected at construction time -- use the default + `'parallel_ordered_events'` or `'sequential'` instead. +- Calling our entry points through an `AbstractAgent`-typed + reference will fail with a `UserError` naming the fix: our + `run` / `iter` / `run_stream` / `run_stream_events` require + `context: WorkflowContext` as the first positional argument, + while the supertype's signature has no `context`. The runtime + check converts what would otherwise be a confusing + `AttributeError` deep in `_agent_run` into an upfront error + message. +- Only one `context.loop(...)` per workflow (a Reboot-wide + limitation, not specific to agents). diff --git a/documentation/docs/learn_more/auth.mdx b/documentation/docs/learn_more/auth.mdx index 9f1e6a85..647c77ae 100644 --- a/documentation/docs/learn_more/auth.mdx +++ b/documentation/docs/learn_more/auth.mdx @@ -82,9 +82,9 @@ const [bank] = await Bank.create( ### From React ```tsx -import { useRebootContext } from "@reboot-dev/reboot-react"; +import { useRebootClient } from "@reboot-dev/reboot-react"; -const { setBearerToken } = useRebootContext(); +const { setBearerToken } = useRebootClient(); setBearerToken(token); ``` @@ -162,16 +162,16 @@ was valid: +(CODE:src=../../../reboot/aio/auth/token_verifiers.py&lines=26-31) --> ```py +@abstractmethod +async def verify_token( + self, + context: ReaderContext, token: Optional[str], ) -> VerifyTokenResult: - """Verify the bearer token. - - Returns: - * `Auth` if the token is valid and the caller is ``` diff --git a/documentation/docs/learn_more/call/from_browser.mdx b/documentation/docs/learn_more/call/from_browser.mdx deleted file mode 100644 index 46719823..00000000 --- a/documentation/docs/learn_more/call/from_browser.mdx +++ /dev/null @@ -1,4 +0,0 @@ -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# From a browser \ No newline at end of file diff --git a/documentation/docs/learn_more/call/from_mcp_client.mdx b/documentation/docs/learn_more/call/from_mcp_client.mdx index c80a006e..c9ff7815 100644 --- a/documentation/docs/learn_more/call/from_mcp_client.mdx +++ b/documentation/docs/learn_more/call/from_mcp_client.mdx @@ -8,6 +8,15 @@ client can connect to it. During development it is easiest to use a local MCP client, since they can easily reach applications running on `localhost`. +:::tip Recommended for rapid iteration + +For tight edit-reload cycles during development, use **MCPJam +Inspector** (below). Claude and ChatGPT aggressively cache your +app's assets, which can make rapid iteration frustrating — +MCPJam picks up changes immediately. + +::: + ### MCPJam Inspector (development) [MCPJam Inspector](https://mcpjam.com) is a browser-based MCP client @@ -18,8 +27,8 @@ Add an `mcp_servers.json` file in your project root, and fill it in: { "mcpServers": { "my-app": { - "type": "streamable-http", - "url": "http://localhost:9991/mcp" + "url": "http://localhost:9991/mcp", + "useOAuth": true } } } @@ -30,7 +39,7 @@ app runs on a different port or host. Then run MCPJam: ```sh -npx @mcpjam/inspector@v2.0.4 \ +npx @mcpjam/inspector@2.4.0 \ --config mcp_servers.json --server my-app ``` @@ -58,49 +67,120 @@ actions. ### Goose - +[Goose](https://block.github.io/goose/) is a desktop AI agent that +supports MCP servers as extensions. To connect your Reboot app: + +1. In Goose, open **Extensions**, then click **+ Add custom + extension**. +2. Enter a name, set **Type** to **Streamable HTTP**, and add a + description. +3. For **Endpoint**, use your MCP server URL — if Goose is + running on the same machine as `rbt dev`, use + `http://localhost:9991/mcp`; otherwise use your Reboot Cloud + URL (`https://your-app.prod1.rbt.cloud:9991/mcp`) or your + `ngrok` tunnel URL with `/mcp` appended. +4. Click **Add extension**. -[Goose](https://block.github.io/goose/) supports MCP servers. Add your -server to the Goose configuration: +Alternatively, you can add the extension directly to Goose's +configuration file: ```yaml extensions: my-app: - type: streamable-http + type: streamable_http uri: http://localhost:9991/mcp ``` +### Claude Desktop + +[Claude Desktop](https://support.claude.com/en/articles/10949351-getting-started-with-local-mcp-servers-on-claude-desktop) +supports MCP servers through its `claude_desktop_config.json` file. +This route is best for local MCP servers that don't require auth +or render `UI` methods — see Claude's documentation for the full +setup. ## On the web -Eventually, you'll likely want to publish your app for popular web-based -AI platforms. This requires that your app is reachable on the internet. -During development, you can use `ngrok`. In production we suggest taking -a look at [Reboot Cloud](/deploy_on_reboot_cloud). +To publish your app to web-based AI platforms, it must be reachable +on the internet. During development, you can use `ngrok`. In +production we suggest taking a look at +[Reboot Cloud](/deploy_on_reboot_cloud). + +:::note Local development with `UI` methods + +If your app has `UI` methods and you're tunneling `rbt dev` through +`ngrok`, the right `.rbtrc` config depends on which client you're +testing with: + +- **ChatGPT** can load UIs from the Vite dev server, so the default + `hmr` config (`uv run rbt dev run`) works and you get hot module + replacement. +- **Claude** renders UIs in a sandboxed iframe that blocks HTTP + script sources, so it can't load from a local Vite dev server. + Build the UIs first with `cd web && npm run build`, then run + `uv run rbt dev run --config=dist` to serve the pre-built + artifacts from `web/dist/`. + +See your app's `.rbtrc` for the `hmr` and `dist` config +definitions. + +::: ### ChatGPT ChatGPT supports -[remote MCP servers](https://developers.openai.com/api/docs/guides/tools-connectors-mcp/). -To connect your deployed Reboot app: +[remote MCP servers](https://developers.openai.com/apps-sdk/deploy/connect-chatgpt) +through its +[Developer Mode](https://developers.openai.com/api/docs/guides/developer-mode), +a beta feature available on Pro, Plus, Business, Enterprise, and Edu +plans. Connecting an app requires Developer Mode to be enabled; on +Business, Enterprise, and Edu workspaces only a workspace owner (or, +on Enterprise/Edu, a user granted access via RBAC) can enable it. +See +[OpenAI's help article](https://help.openai.com/en/articles/12584461-developer-mode-and-mcp-apps-in-chatgpt-beta) +for enablement details. + +Once Developer Mode is on, to connect your deployed Reboot app: 1. Open [chatgpt.com](https://chatgpt.com). -2. In the message composer, click the **Tools** icon (hammer). -3. Select **Add MCP tool**, then enter your MCP server URL - (e.g., `https://your-app.prod1.rbt.cloud:9991/mcp`). -4. ChatGPT will discover your tools and make them available in - the conversation. +2. From your profile menu in the bottom-left corner, click + **Settings**, then **Apps**, then **Advanced Settings** → + **Create App**. +3. Enter a name for your tool, a description, and the URL of your + MCP server. If you're running on Reboot Cloud the URL will look + like `https://your-app.prod1.rbt.cloud:9991/mcp`; if you're + running locally behind an `ngrok` tunnel (see + [exposing `rbt dev` to the internet](/learn_more/nonlocal#ngrok)) + it will be your tunnel's URL with `/mcp` appended. +4. If your app uses auth (`Anonymous` or otherwise), select + **OAuth** from the Authentication dropdown — ChatGPT will run + the OAuth handshake against your Reboot app as soon as you add + the server. Otherwise, select **No auth**. +5. Check **I understand and want to continue**, then create the + app. ChatGPT will discover your tools and make them available + in the conversation. ### Claude (claude.ai) -[Claude.ai](https://claude.ai) supports MCP servers. To connect your -deployed Reboot app: - -1. Open a conversation on claude.ai. -2. Click the **Integrations** icon (puzzle piece) in the message - composer. -3. Add your MCP server URL (e.g., `https://your-app.prod1.rbt.cloud/mcp`). -4. Claude will discover your tools and make them available in the - conversation. +[Claude.ai](https://claude.ai) supports custom connectors backed by +remote MCP servers. They are available on Free, Pro, Max, Team, and +Enterprise plans (Free is limited to one custom connector). On Team +and Enterprise plans only workspace owners can add new custom +connectors. See +[Claude's help article](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp) +for the canonical walkthrough. +To connect your deployed Reboot app: +1. Open [claude.ai](https://claude.ai). +2. Click **Customize**, then **Connectors**, then **+**, then + **Add Custom Connector**. +3. Enter a name and the URL of your MCP server — the URL follows + the same format as for ChatGPT above (e.g., + `https://your-app.prod1.rbt.cloud:9991/mcp` on Reboot Cloud, + or your tunnel URL with `/mcp` appended when running locally). +4. Click **Add**. Claude will automatically detect whether your + app requires auth and discover your tools. +5. To use the connector in a chat, click the **+** button in the + message composer, choose **Connectors**, and toggle your + connector on for that conversation. diff --git a/documentation/docs/learn_more/call/overview.mdx b/documentation/docs/learn_more/call/overview.mdx index cdadf9c2..133bf676 100644 --- a/documentation/docs/learn_more/call/overview.mdx +++ b/documentation/docs/learn_more/call/overview.mdx @@ -149,7 +149,7 @@ the `Open` method as an explicit constructor for `Account`: - + ```python diff --git a/documentation/docs/learn_more/define/methods.mdx b/documentation/docs/learn_more/define/methods.mdx index aea0bf99..84ab47a7 100644 --- a/documentation/docs/learn_more/define/methods.mdx +++ b/documentation/docs/learn_more/define/methods.mdx @@ -35,9 +35,9 @@ The five kinds of methods in Reboot are: ## Tool exposure -Methods on the `User` state type are automatically exposed -as tools callable by the AI. Methods on other state types can -be exposed with `mcp=Tool()`. To prevent a `User` method -from being AI-callable, set `mcp=False`. See +Every method must explicitly declare its MCP exposure with +`mcp=Tool()` (to expose it as an AI-callable tool) or +`mcp=None` (to hide it from the AI). This applies to all +state types, including `User`. See [Creating tools](/learn_more/define/pydantic#creating-tools-for-the-ai) for details. diff --git a/documentation/docs/learn_more/define/pydantic.mdx b/documentation/docs/learn_more/define/pydantic.mdx index 0e6f409e..9c2a1fa9 100644 --- a/documentation/docs/learn_more/define/pydantic.mdx +++ b/documentation/docs/learn_more/define/pydantic.mdx @@ -85,6 +85,7 @@ api = API( "the `counter_id`, which is not " "human-readable but should be passed to " "future tool calls that need it.", + mcp=Tool(), ), list_counters=Reader( request=None, @@ -94,6 +95,7 @@ api = API( "description for each. The `counter_id` " "is not human-readable, but use it when " "calling tools that take a `counter_id`.", + mcp=Tool(), ), ), ), @@ -111,6 +113,7 @@ api = API( request=CreateCounterRequest, response=None, factory=True, + mcp=None, ), get=Reader( request=None, @@ -129,6 +132,7 @@ api = API( description=Reader( request=None, response=DescriptionResponse, + mcp=None, ), ), ), @@ -162,12 +166,10 @@ definition triggers special behavior: `initialize` function or manual `create()` calls. All fields on `UserState` must have default values, since instances are created without arguments. -2. **Auto-tool-exposure**: all `User` methods are - automatically exposed as tools (unless `mcp=False`). -3. **Automatic ID resolution**: the AI never needs to specify a +2. **Automatic ID resolution**: the AI never needs to specify a state ID for `User` methods — it is resolved from the authenticated user. -4. **Default authorization**: `User` methods are accessible to +3. **Default authorization**: `User` methods are accessible to the owning user (whose `user_id` matches the state ID) and to app-internal calls. No `authorizer()` override is needed. See [Default authorizer](/learn_more/auth#default-authorizer) @@ -180,58 +182,37 @@ tool calls. ## Creating tools for the AI -### `User` methods are AI-callable by default - -All methods on the `User` state type are automatically -exposed as MCP tools the AI can call. The `description` field -on each method is what the AI reads to decide when and how to -call a tool: +### Explicit `mcp=` on every method -```python -UserMethods = Methods( - create_counter=Transaction( - request=None, - response=CreateCounterResponse, - # Good: tells the AI what the tool does - # and when to use it. - description="Create a new Counter. Returns " - "the ID of the new counter.", - ), -) -``` +Every method must declare its MCP exposure explicitly: -### Opting out with `mcp=False` +- `mcp=Tool()` — expose the method as an AI-callable tool. +- `mcp=None` — hide the method from the AI. -To prevent a `User` method from being AI-callable, set -`mcp=False`: +This applies to all state types, including `User`. The +`description` field is what the AI reads to decide when and +how to call a tool: ```python +from reboot.api import Tool + UserMethods = Methods( - # AI-callable (default for User methods). + # AI-callable — the AI can call this. create_counter=Transaction( request=None, response=CreateCounterResponse, - description="Create a new Counter.", + description="Create a new Counter. Returns " + "the ID of the new counter.", + mcp=Tool(), ), # NOT AI-callable — internal use only. cleanup=Writer( request=None, response=None, description="Clean up old state.", - mcp=False, + mcp=None, ), ) -``` - -### Exposing non-`User` methods to the AI - -Methods on state types other than `User` are **not** -AI-callable by default. To expose them, add `mcp=Tool()`. -This is how you expose methods on the types that `User` -creates: - -```python -from reboot.api import Tool CounterMethods = Methods( # AI-callable because of `mcp=Tool()`. @@ -241,11 +222,12 @@ CounterMethods = Methods( description="Get the current counter value.", mcp=Tool(), ), - # NOT AI-callable (default for non-User methods). + # NOT AI-callable. create=Writer( request=None, response=None, factory=True, + mcp=None, ), ) diff --git a/documentation/docs/learn_more/idempotency.mdx b/documentation/docs/learn_more/idempotency.mdx index c22a979d..6522ed9e 100644 --- a/documentation/docs/learn_more/idempotency.mdx +++ b/documentation/docs/learn_more/idempotency.mdx @@ -19,30 +19,31 @@ method on a state more than once, including when you're doing manual retries, then you can specify an idempotency "alias" to distinguish each call, for example: - - - +(CODE:src=../../../tests/reboot/idempotency_tests.py&lines=370-373) --> + ```py -bank = Bank.ref(SINGLETON_BANK_ID) - -await bank.sign_up(context, customer_name="Initial User") +await bank.idempotently('sign up jonathan').SignUp( + context, + account_id='jonathan', +) ``` - +(CODE:src=../../../tests/reboot/nodejs/greeter_test/test.ts&lines=64-68) --> + ```ts -await Bank.ref(SINGLETON_BANK_ID).signUp(context, { - customerName: "Initial User", +await Greeter.idempotently("generated").create(context, { + title: "Mr", + name: "John", + adjective: "Dangerous", }); ``` diff --git a/documentation/docs/learn_more/implement/ui_methods.mdx b/documentation/docs/learn_more/implement/ui_methods.mdx index 5f08778f..c0db1d7c 100644 --- a/documentation/docs/learn_more/implement/ui_methods.mdx +++ b/documentation/docs/learn_more/implement/ui_methods.mdx @@ -14,21 +14,17 @@ from a browser or backend. ## Syntax - + ```python -class CreateCounterResponse(Model): - counter_id: str = Field(tag=1) - - -class CounterEntry(Model): - counter_id: str = Field(tag=1) - description: str = Field(tag=2) - - -class ListCountersResponse(Model): - counters: list[CounterEntry] = Field(tag=1, default_factory=list) +show_clicker=UI( + request=None, + path="web/ui/clicker", + title="Counter Clicker", + description="Interactive clicker UI " + "for the counter.", +), ``` @@ -48,17 +44,17 @@ class ListCountersResponse(Model): From the `ai-chat-counter` example: - + ```python -class CreateCounterResponse(Model): - counter_id: str = Field(tag=1) - - -class CounterEntry(Model): - counter_id: str = Field(tag=1) - description: str = Field(tag=2) +show_clicker=UI( + request=None, + path="web/ui/clicker", + title="Counter Clicker", + description="Interactive clicker UI " + "for the counter.", +), ``` @@ -67,21 +63,18 @@ class CounterEntry(Model): From the `ai-chat-counter-dashboard` example: - + ```python -class CreateCounterResponse(Model): - counter_id: str = Field(tag=1) - - -class CounterEntry(Model): - counter_id: str = Field(tag=1) - description: str = Field(tag=2) - - -class ListCountersResponse(Model): - counters: list[CounterEntry] = Field(tag=1, default_factory=list) +show_dashboard=UI( + request=DashboardConfig, + path="web/ui/dashboard", + title="Counter Dashboard", + description="Dashboard UI. Use " + "`personalized_message` to impart wisdom " + "on the topic of counting things.", +), ``` diff --git a/documentation/docs/learn_more/mcp_apps.mdx b/documentation/docs/learn_more/mcp_apps.mdx index 6c22889f..fd62098c 100644 --- a/documentation/docs/learn_more/mcp_apps.mdx +++ b/documentation/docs/learn_more/mcp_apps.mdx @@ -17,16 +17,17 @@ server exposes **tools** (actions the AI can call) and When your Reboot API includes a `User` type, `Application` automatically registers an MCP endpoint at `/mcp`. This means: -- **`User` methods become MCP tools.** All methods on the - `User` type are automatically exposed as MCP tools. The AI - calls them without specifying a state ID — the right `User` - is resolved from the authenticated user. `User` typically - acts as an entry point, with methods that create other state - types. -- **Non-`User` methods can be tools too.** Methods on other - types (like `Counter`) are exposed with `mcp=Tool()`. The AI - receives the state ID when it creates the instance and passes - it in subsequent calls. +- **Methods become MCP tools via `mcp=Tool()`.** Any method + on any type — including `User` — is exposed as an MCP tool + when declared with `mcp=Tool()`. Use `mcp=None` to keep a + method hidden from the AI. `User` methods called without a + state ID resolve the right `User` from the authenticated user. + `User` typically acts as an entry point, with methods that + create other state types. +- **Non-`User` methods work the same way.** Methods on other + types (like `Counter`) also use `mcp=Tool()`. The AI receives + the state ID when it creates the instance and passes it in + subsequent calls. - **`UI` methods become MCP tools + resources.** When the AI calls a `UI` tool, it receives metadata pointing to a compiled React app (served as an MCP resource) — that prompts the AI to @@ -37,9 +38,9 @@ serialization, transport, session management, and state routing. ## Controlling which methods are tools -`User` methods are MCP tools by default — to opt out, set -`mcp=False`. Non-`User` methods are **not** tools by -default — to opt in, set `mcp=Tool()`. +Every method must explicitly declare its MCP exposure: set +`mcp=Tool()` to expose a method as an MCP tool, or `mcp=None` +to keep it hidden from the AI. There is no implicit default. Not every method should be an MCP tool: diff --git a/documentation/docs/learn_more/nonlocal.md b/documentation/docs/learn_more/nonlocal.md index 1c9765bb..6f9c3bcc 100644 --- a/documentation/docs/learn_more/nonlocal.md +++ b/documentation/docs/learn_more/nonlocal.md @@ -74,7 +74,7 @@ No matter what tunnel service you've chosen, it will have given you a new hostname and port for your application. You will use this hostname and port into your frontend's `.env` file, replacing `http://localhost:9991`. For example: ``` -REACT_APP_REBOOT_URL=https://43ee-20-61-126-210.ngrok-free.app +VITE_REBOOT_URL=https://43ee-20-61-126-210.ngrok-free.app ``` You may need to restart your development webserver (e.g. the `npm start` diff --git a/documentation/docs/learn_more/secrets.mdx b/documentation/docs/learn_more/secrets.mdx index 1ea02a9f..1a9f0961 100644 --- a/documentation/docs/learn_more/secrets.mdx +++ b/documentation/docs/learn_more/secrets.mdx @@ -3,122 +3,125 @@ import TabItem from "@theme/TabItem"; # Secrets -Because Reboot applications are cloud-agnostic by default, Reboot provides its own support for storing and reading secret values that your application will need at runtime (such as API keys, passwords, etc). +Reboot provides support for storing and reading secret values that your +application needs at runtime (such as API keys, passwords, etc). -Secrets are stored and retrieved as binary data. +Secrets are environment variables of your choice. For example, a +Mailgun API key might be stored as `MAILGUN_API_KEY`. ## Reading secrets -To retrieve a secret in your Reboot application, use an instance of the `Secrets` class. +To read a secret in your Reboot application, use your language's +standard environment variable API: ```python - from reboot.aio.secrets import Secrets + import os - # Create and reuse a Secrets instance. - SECRETS = Secrets() - - async def uses_a_secret() -> ...: - ... = await SECRETS.get("name-of-the-secret") + api_key = os.environ["MAILGUN_API_KEY"] ``` ```typescript - import { Secrets } from "@reboot-dev/reboot/secrets"; - - // Create and reuse a Secrets instance. - const secrets = new Secrets(); - - async usesASecret() { - ... = await secrets.get("name-of-the-secret"); - } + const apiKey = process.env.MAILGUN_API_KEY; ``` -The `Secrets` class includes a short-lived cache of secret values by default, so it's recommended to: +## Setting secrets -1. Create and reuse a single instance of the `Secrets` class. - * Allows you to take advantage of secret caching. -2. Call `await secrets.get($secret_name)` inline where you want to consume a secret. - * Allows secrets to be updated for your application without restarting (when using - [Reboot Cloud](#secrets-in-reboot-cloud)). +### Setting secrets for local development -## Storing secrets +When running locally with `rbt dev run`, you may set any environment +variable (including secrets) with the `--env` flag: -Secrets can be written locally via two different mechanisms. +```bash +rbt dev run --env=MAILGUN_API_KEY=my-api-key +``` -### Environment variables +You may permanently set a secret for `rbt dev run` by including that +flag in your `.rbtrc`: +``` +# If your dev-time secret is NOT sensitive, you may add it to your +# `.rbtrc` to have it for every `rbt dev run`. +dev run --env=MAILGUN_API_KEY=dummy +``` +:::warning Secrets in `.rbtrc` +Do NOT add sensitive secrets to your `.rbtrc`! Since `.rbtrc` is checked +into your version control, you risk leaking these secrets. Only use +secrets in `.rbtrc` when the value used during `rbt dev run` is not, in +fact, secret! +::: -By default, Reboot will read secrets from environment variables prefixed with -`RBT_SECRET_`: i.e. `RBT_SECRET_MY_SECRET_NAME`. The secret can then be loaded -using a name like `my-secret-name`, `my_secret_name`, etc. +If your development secrets are sensitive, you don't want them in +terminal history or version control. To achieve this, simply set the +environment variable in your terminal in any way you like: -:::note -Before being loaded from an environment variable, the secret name will be upper-cased, and -dashes (`-`) will be replaced with underscores (`_`). -::: +```bash +export MAILGUN_API_KEY=$(pass show -c mailgun/api-key) +rbt dev run --application=backend/src/main.py +``` + +### Setting secrets in Reboot Cloud -### A directory +Use the `rbt cloud secret` CLI commands to manage secrets for a deployed +application: + +```bash +# Set secrets by name, reading values from your environment. +# This avoids exposing secret values in shell history or +# process listings. +export MAILGUN_API_KEY=$(pass show -c mailgun/api-key) +rbt cloud secret set \ + --organization="ACME" \ + --application-name=my-app \ + MAILGUN_API_KEY + +# Or pass values inline (but note: KEY=VALUE on the command line may be +# visible in shell history). +rbt cloud secret set \ + --organization="ACME" \ + --application-name=my-app \ + MAILGUN_API_KEY=my-api-key + +# List secret names (values are never shown). +rbt cloud secret list \ + --organization="ACME" \ + --application-name=my-app + +# Delete secrets. +rbt cloud secret delete \ + --organization="ACME" \ + --application-name=my-app \ + MAILGUN_API_KEY +``` -To load secrets from a directory on disk instead, you can pass the -`--secrets-directory=...` flag to `rbt dev run` or `rbt serve`. +Secret names must be uppercase environment variable names: letters, +digits, and underscores; not starting with a digit. They must not start +with `REBOOT_` or `RBT_` -- those names are reserved by Reboot. -The given directory should contain secret files to use, with filenames -corresponding exactly to secret names. +Setting or deleting secrets on a running application triggers an +automatic restart so the new values take effect. -## Testing secrets +## Testing with secrets -You can provide mock secrets values in tests using the `MockSecretSource`: +In tests, set the environment variable before starting the Reboot test +harness: ```python - from reboot.aio.secrets import Secrets, MockSecretSource - - def setUp() -> None: - Secrets.set_secret_source( - MockSecretSource( - {"my-secret-name": "my-secret-value".encode()} - ) - ) + import os + + os.environ["MAILGUN_API_KEY"] = "test-api-key" ``` ```typescript - import { Secrets, MockSecretSource } from "@reboot-dev/reboot/secrets"; - - Secrets.setSecretSource( - new MockSecretSource({ [secretName]: Buffer.from(secretValue, "utf8") }) - ); + process.env.MAILGUN_API_KEY = "test-api-key"; ``` - -## Secrets in Reboot Cloud - -### Storing secrets - -To store a secret securely in Reboot Cloud, use the `rbt cloud secret` subcommands: - -```bash -rbt cloud secret write \ - --api-key=${REBOOT_CLOUD_API_KEY} \ - --secret-name=example \ - --secret-value=ex4mple -``` - -Secret values are encrypted at rest. When passed using `--secret-value`, the secret string value is UTF-8 encoded. You can also store a secret directly from a file using `--secret-value-file`. - -### Runtime environments - -If you have stored secrets in Reboot Cloud, when running your application via `rbt dev run`, you need to pass your API key in order to read them: -```bash -rbt dev run \ - --api-key=${REBOOT_CLOUD_API_KEY} -``` - -When running via `rbt cloud up`, a short-lived API key is automatically provided to your application at runtime. diff --git a/documentation/docs/learn_more/testing.md b/documentation/docs/learn_more/testing.md index 8f7fdc4c..2870dda7 100644 --- a/documentation/docs/learn_more/testing.md +++ b/documentation/docs/learn_more/testing.md @@ -50,25 +50,18 @@ async def test_chat_room(self) -> None: -#### Mocking Secrets +#### Setting Secrets -Some servicers may use secrets for connecting to external services or handling -sensitive data. You can mock secrets using -`reboot.aio.secrets.MockSecretSource`, which lets you override the secrets with -values you provide. +Some servicers may use secrets for connecting to external services or +handling sensitive data. In tests, set the required environment +variables before starting the test harness. +(CODE:src=../../../reboot/examples/boutique/backend/tests/full_app_test.py&lines=25-25) --> ```py -Secrets.set_secret_source( - MockSecretSource( - { - MAILGUN_API_KEY_SECRET_NAME: MAILGUN_API_KEY.encode(), - } - ) -) +os.environ["MAILGUN_API_KEY"] = MAILGUN_API_KEY ``` diff --git a/documentation/docs/learn_more/typescript_mcp.mdx b/documentation/docs/learn_more/typescript_mcp.mdx deleted file mode 100644 index 27b6dd75..00000000 --- a/documentation/docs/learn_more/typescript_mcp.mdx +++ /dev/null @@ -1,3 +0,0 @@ -# TypeScript - -## Coming Soon! diff --git a/documentation/docs/library_services/coming_soon/list.md b/documentation/docs/library_services/coming_soon/list.md deleted file mode 100644 index e6fa148c..00000000 --- a/documentation/docs/library_services/coming_soon/list.md +++ /dev/null @@ -1,15 +0,0 @@ -# List - -A larger-than-memory `List`. - -To imitate a `List` for: -* smaller-than-memory collections: use Protobuf's support for - [`repeated`](https://protobuf.dev/programming-guides/proto2/#field-labels) values in your - state type. -* larger-than-memory collections: use the Reboot standard library [`SortedMap`](/library_services/sorted_map) type with - time-ordered [UUIDv1](https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_1_and_6_(date-time_and_MAC_address)) keys. - ----- - -_If you'd like us to prioritize this standard library feature or you want to work -with us on it, [reach out to us on Discord.](https://discord.gg/cRbdcS94Nr)_ diff --git a/documentation/docs/library_services/coming_soon/presence.md b/documentation/docs/library_services/coming_soon/presence.md deleted file mode 100644 index ef9cc58d..00000000 --- a/documentation/docs/library_services/coming_soon/presence.md +++ /dev/null @@ -1,8 +0,0 @@ -# Presence - -Support for tracking the realtime presence of users in browsers or other clients. - ----- - -_If you'd like us to prioritize this standard library feature or you want to work -with us on it, [reach out to us on Discord.](https://discord.gg/cRbdcS94Nr)_ diff --git a/documentation/docs/library_services/coming_soon/stripe.md b/documentation/docs/library_services/coming_soon/stripe.md deleted file mode 100644 index 0f0de21b..00000000 --- a/documentation/docs/library_services/coming_soon/stripe.md +++ /dev/null @@ -1,8 +0,0 @@ -# Stripe - -Support for transactionally interacting with [`Stripe`](https://stripe.com/). - ----- - -_If you'd like us to prioritize this standard library feature or you want to work -with us on it, [reach out to us on Discord.](https://discord.gg/cRbdcS94Nr)_ diff --git a/documentation/docs/library_services/coming_soon/twilio.md b/documentation/docs/library_services/coming_soon/twilio.md deleted file mode 100644 index ba279b61..00000000 --- a/documentation/docs/library_services/coming_soon/twilio.md +++ /dev/null @@ -1,8 +0,0 @@ -# Twilio - -Support for transactionally interacting with [`Twilio`](https://www.twilio.com/). - ----- - -_If you'd like us to prioritize this standard library feature or you want to work -with us on it, [reach out to us on Discord.](https://discord.gg/cRbdcS94Nr)_ diff --git a/documentation/docs/library_services/how_to.md b/documentation/docs/library_services/how_to.md deleted file mode 100644 index cd2f0330..00000000 --- a/documentation/docs/library_services/how_to.md +++ /dev/null @@ -1,4 +0,0 @@ -# Creating an Integration - -Coming soon! - diff --git a/documentation/docs/library_services/mailgun.md b/documentation/docs/library_services/mailgun.md index 22f1b509..97904ff4 100644 --- a/documentation/docs/library_services/mailgun.md +++ b/documentation/docs/library_services/mailgun.md @@ -6,9 +6,10 @@ Third-party integration that supports sending email messages using the Mailgun API. -To use the Mailgun integration, store your Mailgun API key as a secret -named `mailgun-api-key`, and use that secret to authenticate to the -integration. +To use the Mailgun integration, set the `MAILGUN_API_KEY` +environment variable to your Mailgun API key. See [Secrets](../learn_more/secrets.mdx) +for how to set this for local development and for deployments to +Reboot Cloud. ## Message {#rbtthirdpartymailgunv1message} A single message sent using the integration. @@ -65,6 +66,7 @@ self.assertEqual(1, len(MockMessageServicer.emails_sent)) ``` :::note -When using `MockMessageServicer`, ensure that the `mailgun-api-key` secret is -available in the Secrets servicer. +When using `MockMessageServicer`, ensure that the `MAILGUN_API_KEY` +environment variable is set (any non-empty value works for the +mock). ::: diff --git a/documentation/docs/library_services/queue.mdx b/documentation/docs/library_services/queue.mdx index 241c91a6..c84e1189 100644 --- a/documentation/docs/library_services/queue.mdx +++ b/documentation/docs/library_services/queue.mdx @@ -22,15 +22,27 @@ To use a `Queue`, import the library where you would like to use it. + + + ```py from reboot.std.collections.queue.v1.queue import Queue from reboot.std.item.v1.item import Item ``` + + + + + ```ts -import { Queue } from "@reboot-dev/reboot-std/collections/queue/v1"; +import queue, { Queue } from "@reboot-dev/reboot-std/collections/queue/v1"; ``` + + @@ -69,16 +81,28 @@ and `"my-second-queue"`. You can then call methods on these references. + + + ```py first_queue = Queue.ref("my-first-queue") second_queue = Queue.ref("my-second-queue") ``` + + + + + ```ts const firstQueue = Queue.ref("my-first-queue"); const secondQueue = Queue.ref("my-second-queue"); ``` + + @@ -89,10 +113,16 @@ const secondQueue = Queue.ref("my-second-queue"); Enqueues a single [`Item`](/library_services/item) directly from a `Value`, `bytes`, or `Any`. +NOTE: `Any` is only supported in Python, not TypeScript. + + + + ```py -from reboot.protobuf import from_dict +from reboot.protobuf import from_dict, pack await first_queue.enqueue( context, @@ -101,13 +131,26 @@ await first_queue.enqueue( await second_queue.enqueue(context, bytes=b"my-bytes") -await third_queue.enqueue(context, any=) +await third_queue.enqueue(context, any=pack(any)) ``` + + + + + ```ts import { Value } from "@bufbuild/protobuf"; +``` + + + + +```ts await firstQueue.enqueue(context, { value: Value.fromJson({ details: "details-go-here" }), }); @@ -115,11 +158,9 @@ await firstQueue.enqueue(context, { await secondQueue.enqueue(context, { bytes: new TextEncoder().encode("my-bytes"), }); - -await thirdQueue.enqueue(context, { - any: , -}); ``` + + @@ -127,8 +168,14 @@ await thirdQueue.enqueue(context, { Dequeues a single [`Item`](/library_services/item). +NOTE: `Any` is only supported in Python, not TypeScript. + + + + ```py from reboot.protobuf import as_dict @@ -141,18 +188,23 @@ print(item.bytes) item = await third_queue.dequeue(context) print(item.any) ``` + + + + + ```ts const { value } = await firstQueue.dequeue(context); console.log(value?.toJson()["details"]); const { bytes } = await secondQueue.dequeue(context); console.log(bytes); - -const { any } = await thirdQueue.dequeue(context); -console.log(any); ``` + + @@ -160,10 +212,23 @@ console.log(any); Enqueues a list of [`Item`](/library_services/item)s. +NOTE: `Any` is only supported in Python, not TypeScript. + + + + ```py -from reboot.protobuf import from_bool, from_dict, from_int, from_list, from_str +from reboot.protobuf import ( + from_bool, + from_dict, + from_int, + from_list, + from_str, + pack, +) await first_queue.enqueue( context, @@ -187,16 +252,29 @@ await second_queue.enqueue( await third_queue.enqueue( context, items=[ - Item(any=), - Item(any=), + Item(any=pack(any)), + Item(any=pack(any)), ], ) ``` + + + + + ```ts import { Value } from "@bufbuild/protobuf"; +``` + + + + +```ts await firstQueue.enqueue(context, { items: [ { value: Value.fromJson(null) }, @@ -208,16 +286,15 @@ await firstQueue.enqueue(context, { ], }); -await secondQueue.enqueue(context, { items: [ - { bytes: new TextEncoder().encode("some-bytes") }, - { bytes: new TextEncoder().encode("some-more-bytes") }, -]}); - -await thirdQueue.enqueue(context, { items: [ - { any: }, - { any: }, -]}); +await secondQueue.enqueue(context, { + items: [ + { bytes: new TextEncoder().encode("some-bytes") }, + { bytes: new TextEncoder().encode("some-more-bytes") }, + ], +}); ``` + + @@ -225,20 +302,34 @@ await thirdQueue.enqueue(context, { items: [ By specifying `bulk` and `at_most`/`atMost`, you can also dequeue [`Item`](/library_services/item)s in bulk. +NOTE: `Any` is only supported in Python, not TypeScript. + + + + ```py items = await first_queue.dequeue(context, bulk=True, at_most=5) # items.items is a list of Items ``` + + + + + ```ts const { items } = await firstQueue.dequeue(context, { bulk: true, atMost: 5, -}) +}); // `items` is a list of Items ``` + + diff --git a/documentation/docs/standard_library.mdx b/documentation/docs/standard_library.mdx deleted file mode 100644 index bcb8efa2..00000000 --- a/documentation/docs/standard_library.mdx +++ /dev/null @@ -1,4 +0,0 @@ -import Tabs from "@theme/Tabs"; -import TabItem from "@theme/TabItem"; - -# Using the standard library diff --git a/documentation/sidebars.js b/documentation/sidebars.js index 8e472ee9..afbd1af9 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -103,7 +103,6 @@ const sidebars = { items: [ "learn_more/call/overview", "learn_more/call/from_react", - // "learn_more/call/from_browser", "learn_more/call/from_within_your_app", "learn_more/call/from_outside_your_app", "learn_more/call/via_http", @@ -117,6 +116,7 @@ const sidebars = { "learn_more/tasks", "learn_more/side_effects", "learn_more/idempotency", + "learn_more/agents", ], }, { diff --git a/mypy.ini b/mypy.ini index 649d1039..546bca2b 100644 --- a/mypy.ini +++ b/mypy.ini @@ -116,6 +116,8 @@ ignore_missing_imports = True ignore_missing_imports = True [mypy-pulumi_random.*] ignore_missing_imports = True +[mypy-pydantic_ai.*] +ignore_missing_imports = True [mypy-pyprctl.*] ignore_missing_imports = True [mypy-requests.*] diff --git a/package.json b/package.json index b5306b87..1047f295 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ { "private": true, "dependencies": { - "@reboot-dev/reboot-api": "workspace:*", - "@reboot-dev/reboot-react": "workspace:*", - "@reboot-dev/reboot-std-api": "workspace:*", - "@reboot-dev/reboot-std-react": "workspace:*", - "@reboot-dev/reboot-std": "workspace:*", - "@reboot-dev/reboot-web": "workspace:*", - "@reboot-dev/reboot": "workspace:*", - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", + "@reboot-dev/reboot-std-api": "1.0.3", + "@reboot-dev/reboot-std-react": "1.0.3", + "@reboot-dev/reboot-std": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", + "@reboot-dev/reboot": "1.0.3", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", "@bufbuild/protobuf": "1.10.1", "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 11692365..9c1369d3 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,3 +7,4 @@ packages: - "reboot/std/" - "reboot/std/react/" - "reboot/web/" + - "reboot/create-ui/" diff --git a/rbt/cloud/v1alpha1/application/application.proto b/rbt/cloud/v1alpha1/application/application.proto index 662afb98..bd7eaae9 100644 --- a/rbt/cloud/v1alpha1/application/application.proto +++ b/rbt/cloud/v1alpha1/application/application.proto @@ -104,6 +104,18 @@ message Application { // Live metrics, updated every 30 seconds while the application is UP. ApplicationMetrics metrics = 10; + + // The `task_uuid` of the most recently scheduled `metrics_workflow`. + // `metrics_workflow` compares its own `context.task_id` against this + // to detect that a newer instance has been scheduled and exit, ensuring + // at most one `metrics_workflow` runs per application at a time. + bytes latest_metrics_workflow_task_uuid = 11; + + // The workflow (task) UUID of the workflow currently holding an + // exclusive lock on this state. In string representation for human + // readability. No other writers or workflows should edit the state if + // this field is non-emtpy. + optional bytes locked_for_workflow_task_uuid = 12; } /////////////////////////////////////////////////////////////////////// @@ -211,6 +223,37 @@ service ApplicationMethods { workflow: {}, }; } + + // Store one or more secrets for this application. Keys must be + // uppercase environment variable names (letters, digits, and + // underscores; not starting with a digit). They must not start + // with "REBOOT_" or "RBT_", and must not be exactly "REBOOT", + // "RBT", "PORT", or "PYTHONUNBUFFERED" -- those names are + // reserved by the Reboot platform. If specified keys already + // exist, they are overwritten. Any keys that are not specified + // are left as-is (use `DeleteSecrets` to remove them). + rpc SetSecrets(SetSecretsRequest) returns (SetSecretsResponse) { + option (rbt.v1alpha1.method) = { + workflow: {}, + errors: [ "InvalidInputError" ], + }; + } + + // Return (only) the names of all stored secrets. + rpc ListSecrets(ListSecretsRequest) returns (ListSecretsResponse) { + option (rbt.v1alpha1.method) = { + reader: {}, + }; + } + + // Remove one or more secrets. Unknown names are silently ignored; + // no validation is performed on the input names. + rpc DeleteSecrets(DeleteSecretsRequest) returns (DeleteSecretsResponse) { + option (rbt.v1alpha1.method) = { + workflow: {}, + errors: [ "InvalidInputError" ], + }; + } } /////////////////////////////////////////////////////////////////////// @@ -379,6 +422,21 @@ message IsAdminResponse { bool is_admin = 1; } +message SetSecretsRequest { + map secrets = 1; +} +message SetSecretsResponse {} + +message ListSecretsRequest {} +message ListSecretsResponse { + repeated string names = 1; +} + +message DeleteSecretsRequest { + repeated string names = 1; +} +message DeleteSecretsResponse {} + ////////////////////////////////////////////////////////////////////// message InvalidInputError { diff --git a/rbt/std/package.json b/rbt/std/package.json index 988ecd5e..8d79be29 100644 --- a/rbt/std/package.json +++ b/rbt/std/package.json @@ -1,6 +1,6 @@ { "name": "@reboot-dev/reboot-std-api", - "version": "0.46.0", + "version": "1.0.3", "description": "Reboot standard library API.", "main": "index.js", "type": "module", diff --git a/rbt/v1alpha1/package.json b/rbt/v1alpha1/package.json index c1fde32d..26d47e65 100644 --- a/rbt/v1alpha1/package.json +++ b/rbt/v1alpha1/package.json @@ -1,6 +1,6 @@ { "name": "@reboot-dev/reboot-api", - "version": "0.46.0", + "version": "1.0.3", "type": "module", "description": "npm package for Reboot API", "main": "index.js", diff --git a/reboot-skills/skills/reboot-chat-app/SKILL.md b/reboot-skills/skills/reboot-chat-app/SKILL.md index d383dbe3..31fc32e8 100644 --- a/reboot-skills/skills/reboot-chat-app/SKILL.md +++ b/reboot-skills/skills/reboot-chat-app/SKILL.md @@ -97,7 +97,13 @@ Before writing code, analyze the user's request: application types? Each gets a `Transaction` on `User` that calls `.create(context)`. 3. **State shape**: Fields, types — lists, nested objects, - primitives. Each gets `Field(tag=N)`. + primitives. Each gets `Field(tag=N)`. **Nested Model + subobjects** (preferences, profile, config — owned 1:1 by a + parent state) must be `Optional[X] = Field(tag=N, default=None)` + and hydrated in the parent's factory `create` Writer; nested + Models can't take `default=` or `default_factory=` (Gotcha #21). + For collections, prefer `list[Item]` with `default_factory=list` + over a single-nested wrapper. 4. **Operations**: Map to the right method type: - `Reader` — read-only queries - `Writer` — single-state mutations @@ -123,21 +129,20 @@ types: state is typically empty. Its methods are `Transaction`s that create instances of application types, or `Reader`s that find the IDs of existing application type instances in indexes that - have well-known IDs of their own. `User` methods are - automatically exposed as MCP tools. + have well-known IDs of their own. - **Application types** (e.g., `Counter`) hold the actual application state. They need a `create` Writer with - `factory=True` for construction. Their methods require explicit - `mcp=Tool()` to be AI-callable. + `factory=True` for construction. ### Tool Exposure Control -- **User methods are tools by default.** All methods on - `User` are automatically callable by the AI. -- **`mcp=False`**: Opt a User method OUT of being AI-callable. - Use for human-only actions or to reduce context bloat. -- **`mcp=Tool()`**: Opt an application type method IN to being - AI-callable. Required on methods of non-User types. +Every method must explicitly declare its MCP exposure: + +- **`mcp=Tool()`**: Expose the method as an AI-callable tool. + Required on every method — including `User` methods — that + the AI should be able to call. +- **`mcp=None`**: Hide the method from the AI. Use for + human-only actions or to reduce context bloat. - **`Tool()` options**: `Tool(name="custom_name", title="Title")` to override the default tool name or add a human-readable title. @@ -168,8 +173,8 @@ No `@mcp.tool()` decorators. ### State is Durable -State survives restarts. Set `dev run --name=` in `.rbtrc` to -persist across dev restarts. Use `uv run rbt dev expunge --name=` to reset. +State survives restarts. Set `dev run --application-name=` in `.rbtrc` to +persist across dev restarts. Use `uv run rbt dev expunge --application-name=` to reset. ## Project Structure @@ -212,19 +217,19 @@ application directory.** 4. `uv run rbt generate` 5. Write servicer (`backend/src/servicers/.py`) 6. Write `main.py` -7. Scaffold web: `package.json`, tsconfigs, `vite.config.ts`, - `index.css` +7. `npm create @reboot-dev/ui` 8. `cd web && npm install` 9. `uv run rbt generate` (React bindings need `node_modules`) -10. Write React: `index.html`, `main.tsx`, `App.tsx`, - `App.module.css` +10. Customize React UIs: edit `App.tsx` files in `web/ui/*/` 11. `cd web && npm run build` 12. Create `mcp_servers.json` with - `{"mcpServers":{"":{"type":"streamable-http","url":"http://localhost:9991/mcp"}}}` + `{"mcpServers":{"":{"url":"http://localhost:9991/mcp","useOAuth":true}}}` 13. **STOP.** Do NOT run the app yourself. Print the following run instructions exactly, then wait: ``` + Project directory: + To run (each in a separate terminal, from the project directory): uv run rbt dev run # start backend @@ -232,13 +237,14 @@ application directory.** To test with MCP inspector (separate terminal): - npx @mcpjam/inspector@v2.0.4 --config mcp_servers.json --server + npx @mcpjam/inspector@2.4.0 --config mcp_servers.json --server ``` Replace `` with the actual server name from - `mcp_servers.json`. Then suggest a first prompt the user - can try in the inspector (e.g., "Create a new todo list - and show it to me"). + `mcp_servers.json`. Print the real absolute path of the + project directory (not the literal placeholder). Then + suggest a first prompt the user can try in the inspector + (e.g., "Create a new todo list and show it to me"). ## Inline Patterns @@ -273,7 +279,7 @@ dev run --watch=backend/**/*.py dev run --python # Save state between restarts. -dev run --name= +dev run --application-name= # Run the application! dev run --application=backend/src/main.py @@ -292,7 +298,7 @@ dev run:hmr --mcp-frontend-host=http://localhost:4444 dev run:dist --mcp-frontend-host="" # When expunging, expunge that state we've saved. -dev expunge --name= +dev expunge --application-name= ``` ### `pyproject.toml` @@ -306,14 +312,14 @@ dependencies = [ "httpx>=0.27,<1.0", "uuid7>=0.1.0", "anyio>=4.0.0", - "reboot>=0.46.0", + "reboot>=1.0.3", ] [tool.rye] dev-dependencies = [ "mypy==1.18.1", "types-protobuf>=4.24.0.20240129", - "reboot>=0.46.0", + "reboot>=1.0.3", ] virtual = true @@ -330,7 +336,8 @@ Rules: - `User` type with empty state and `Transaction` methods that create application type instances - Application types with their own state and methods -- Application type methods need `mcp=Tool()` to be AI-callable +- All methods need explicit `mcp=Tool()` (AI-callable) or + `mcp=None` (hidden from AI) - Application types need a `create` Writer with `factory=True` - `api = API(User=Type(...), =Type(...))` @@ -367,6 +374,7 @@ class UserState(Model): class CounterState(Model): value: int = Field(tag=1, default=0) + description: str = Field(tag=2, default="") class ValueResponse(Model): @@ -390,6 +398,7 @@ api = API( "human-readable; pass it to future tool " "calls where needed, but no need to tell " "the human what it is.", + mcp=Tool(), ), ), ), @@ -407,6 +416,7 @@ api = API( request=None, response=None, factory=True, + mcp=None, ), get=Reader( request=None, @@ -477,7 +487,7 @@ export const DashboardApp: FC = ({ personalizedMessage }) => { }; ``` -#### mcp=False Example +#### mcp=None Example Hide a method from the AI (e.g., for human-only actions): @@ -488,7 +498,7 @@ confirm_dangerous_action=Writer( request=ConfirmRequest, response=None, description="Confirm a dangerous action.", - mcp=False, + mcp=None, ), ``` @@ -510,6 +520,53 @@ The counter example above shows the full User + application type pattern. Apply the same structure for any application type, adding whatever Writers and Readers your app needs. +#### Nested Model State Patterns + +For application types that own a single nested `Model` +sub-object (preferences blob, profile, config, etc.): + +- Declare the field as `Optional[Sub] = Field(tag=N, default=None)`. + Nested non-Optional `Model` types reject both `default=` and + `default_factory=` (Gotcha #21). +- Hydrate the sub-object in the parent's factory `create` + Writer, so callers never observe the `None`: + +```python +from reboot.api import API, Field, Methods, Model, Transaction, Type, User as RbtUser, Writer +from typing import Optional + +class GuestPreferences(Model): + meal_type: str = Field(tag=1, default="") + calorie_level: str = Field(tag=2, default="") + dietary_restrictions: str = Field(tag=3, default="") + +class Guest(Model): + name: str = Field(tag=1, default="") + # Single nested Model: Optional + default=None, populated + # by the factory `create` below. + preferences: Optional[GuestPreferences] = Field(tag=2, default=None) + +class CreateRequest(Model): + name: str = Field(tag=1) + meal_type: str = Field(tag=2, default="") + calorie_level: str = Field(tag=3, default="") + dietary_restrictions: str = Field(tag=4, default="") + +# Servicer side (in `backend/src/servicers/.py`): +class GuestServicer(Guest.Servicer): + async def create(self, context, *, name, meal_type, calorie_level, dietary_restrictions): + self.state.name = name + self.state.preferences = GuestPreferences( + meal_type=meal_type, + calorie_level=calorie_level, + dietary_restrictions=dietary_restrictions, + ) +``` + +If the prompt suggests _plural_ sub-objects ("each guest's +preferences"), prefer `list[GuestPreferences]` with +`default_factory=list` — lists are exempt from this rule. + #### Workflow Example (Long-Running) Use `Workflow` for periodic or long-running operations: @@ -552,7 +609,7 @@ Rules: `from .v1._rbt import User, Counter` - Each type gets its own servicer class (e.g., `UserServicer`, `CounterServicer`) -- `User.Servicer` / `Counter.Servicer` base, `allow()` authorizer +- `User.Servicer` / `Counter.Servicer` base - Context types from `reboot.aio.contexts`: - `ReaderContext` — read-only - `WriterContext` — single-state mutation @@ -566,7 +623,6 @@ Rules: ```python from ai_chat_counter.v1.counter_rbt import Counter, User -from reboot.aio.auth.authorizers import allow from reboot.aio.contexts import ( ReaderContext, TransactionContext, @@ -576,14 +632,15 @@ from reboot.aio.contexts import ( class UserServicer(User.Servicer): - def authorizer(self): - return allow() - async def create_counter( self, context: TransactionContext, ) -> User.CreateCounterResponse: """Create a new Counter and return its ID.""" + # Factory create: pass request fields as keyword args + # directly — do NOT wrap in a Request object. + # No-args: Counter.create(context) + # With args: Counter.create(context, title="...", count=0) counter, _ = await Counter.create(context) return User.CreateCounterResponse( counter_id=counter.state_id, @@ -592,9 +649,6 @@ class UserServicer(User.Servicer): class CounterServicer(Counter.Servicer): - def authorizer(self): - return allow() - async def create(self, context) -> None: # State is initialized with defaults; nothing to do. pass @@ -622,34 +676,138 @@ class CounterServicer(Counter.Servicer): #### Workflow Servicer -Workflow methods are `@classmethod` — no `self.state` access: +Workflow methods are `@classmethod` — no `self`, no `self.state`. To +call back into the current instance, use the **state class** +imported from `_rbt` (e.g. `MyType.ref()`), NOT `cls`. Inside a +Workflow, calling `.ref()` with no arguments is special: +it picks up the current `state_id` from `WorkflowContext` +automatically, so `MyType.ref()` resolves to a ref to the running +workflow's own instance. ```python from datetime import timedelta from reboot.aio.contexts import WorkflowContext +# Import the state class — this is what `.ref()` is called on, +# NOT `cls`. `cls` inside the classmethod is `MyTypeBaseServicer`. +from .v1._rbt import MyType + + +class MyTypeServicer(MyType.Servicer): + + @classmethod + async def do_ping_periodically( + cls, + context: WorkflowContext, + request: MyType.DoPingPeriodicallyRequest, + ) -> MyType.DoPingPeriodicallyResponse: + async for iteration in context.loop( + "Ping periodically", + interval=timedelta(seconds=request.period_seconds), + ): + # `MyType.ref()` with no args is Workflow-only magic: + # it reads `state_id` from `WorkflowContext`, returning + # a ref to this workflow's own instance. Do NOT write + # `cls.ref()` or `self.ref()` here — see Critical + # Gotcha #19. + await MyType.ref().do_ping(context) + pings_sent = iteration + 1 # iteration starts at 0. + if pings_sent >= request.num_pings: + break + + # `.read()` is only valid on the workflow's own no-arg + # ref; a foreign-state read like + # `OtherType.ref(id).read(context)` raises a "only + # supported within workflows" RuntimeError. Call a Reader + # method on the foreign type instead — see Gotcha #23. + state = await MyType.ref().read(context) + return MyType.DoPingPeriodicallyResponse( + num_pings=state.num_pings, + ) +``` -# In the application type's servicer: -@classmethod -async def do_ping_periodically( - cls, - context: WorkflowContext, - request: MyType.DoPingPeriodicallyRequest, -) -> MyType.DoPingPeriodicallyResponse: - async for iteration in context.loop( - "Ping periodically", - interval=timedelta(seconds=request.period_seconds), - ): - await MyType.ref().do_ping(context) - pings_sent = iteration + 1 # iteration starts at 0 - if pings_sent >= request.num_pings: - break +**Use inline writers for workflow-only state changes.** When the +mutation is only ever performed by this workflow, do _not_ add a +separate `store_xxx` Writer to the API just so the workflow can +call it. Pass an `async (state) -> ...` function to +`.idempotently("alias").write(context, fn)`: - state = await MyType.ref().read(context) - return MyType.DoPingPeriodicallyResponse( - num_pings=state.num_pings, - ) +```python +async def increment_count(state): + state.num_pings += 1 + +await MyType.ref().idempotently( + "Increment ping count", +).write(context, increment_count) +``` + +The idempotency alias is a human-readable string that survives +workflow restarts — the inline writer runs at most once per +alias. Anti-pattern: defining a `store_count` Writer in the API +just so the workflow can `await MyType.ref().store_count(context)` +to bump a counter. That adds an unnecessary indirection. +Reserve declared Writers for operations that are also called +from outside the workflow. + +For "run every time" (e.g., re-fetching a remote value on each +loop iteration), use `.always().write(context, fn)` instead of +`.idempotently("...").write(...)`. + +#### Scheduling a Workflow from a Transaction + +A workflow can only be `await`-ed directly from an +`ExternalContext` (e.g. a bootstrap script) or from another +`WorkflowContext`. In **any** other context — most commonly a +`TransactionContext` kicking off a workflow on a state it just +created — the workflow must be **scheduled**, not awaited +directly. Use `.schedule()` to fire-and-forget from a +transaction: + +```python +# In a User's Transaction method that creates a Game and wants +# its autoplay workflow to start running: +class UserServicer(User.Servicer): + + async def create_game( + self, + context: TransactionContext, + request: User.CreateGameRequest, + ) -> User.CreateGameResponse: + game, _ = await Game.create(context, ...) + # GOOD — schedule the workflow from the transaction. + # Request type is empty (`AutoplayRequest`), so just + # pass `context`: + await Game.ref(game.state_id).schedule().autoplay(context) + # If the workflow request had fields (e.g. + # `do_ping_periodically(num_pings, period_seconds)`), + # pass them as keyword args: + await Game.ref(game.state_id).schedule().do_ping_periodically( + context, + num_pings=10, + period_seconds=1.0, + ) + # BAD — wrapping in `request=` raises + # `TypeError: ... got an unexpected keyword argument + # 'request'` (Gotcha #9): + # await Game.ref(game.state_id).schedule().autoplay( + # context, request=Game.AutoplayRequest() + # ) + # BAD — awaiting a workflow directly from a Transaction + # raises `TypeError: ... 'Autoplay' is a workflow and + # must be scheduled from a 'TransactionContext' via + # `await [...].schedule([...]).Autoplay(context, [...])` + # (Gotcha #20): + # await Game.ref(game.state_id).autoplay(context) + return User.CreateGameResponse(game_id=game.state_id) ``` +`.schedule(when=timedelta(...))` delays the workflow by a +duration. `.schedule()` with no argument starts it as soon as +the transaction commits. + +Same rule from a `WriterContext` or `ReaderContext`: use +`.schedule()`. Only `ExternalContext` and `WorkflowContext` can +await a workflow directly. + ### `main.py` Register all servicers (User + application types): @@ -682,6 +840,10 @@ if __name__ == "__main__": ### `web/package.json` +**Use explicit per-UI build scripts as shown below. Do NOT create a +`build.js` or any auto-discovery wrapper — use `npm run build:` +scripts directly.** + ```json { "name": "-web", @@ -696,10 +858,10 @@ if __name__ == "__main__": "build:watch": "concurrently \"npm:build:watch:*\"" }, "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-react": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-react": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", "zod": "^3.25.0" @@ -725,6 +887,11 @@ for each UI, and update the `build` script to chain them: ### `web/vite.config.ts` +**CRITICAL: Copy this file EXACTLY. Do NOT refactor, generalize, or +add recursive directory scanning. The flat `outDir: "dist"` and +`output: "${name}.html"` pattern is required — nested output paths +will break the MCP server's UI discovery.** + ```typescript // Vite configuration for Reboot UIs. import fs from "fs"; @@ -1216,6 +1383,12 @@ Adapt the CSS module to your app's needs. The CSS variables from all `.py` files; `__init__.py` causes conflicts. 3. **`Field(tag=N)` required on every field.** Tags must be unique within each Model class. Start at 1. + **Defaults:** State fields must use proto3 zero-value defaults: + `default=0` for int, `default=""` for str, `default=False` for + bool, `default_factory=list` for lists. Non-zero defaults like + `default="red"` or `default=1` are NOT supported (protobuf + limitation). Set initial values in a servicer method marked as + a factory instead. Request/response fields need no default. 4. **Helper Model types are standalone imports:** `from .v1. import MyItem` — NOT `Counter.MyItem` (that doesn't exist). @@ -1223,27 +1396,116 @@ Adapt the CSS module to your app's needs. The CSS variables from `.XxxRequest`, `.XxxResponse`. 6. **React bindings use camelCase:** Python `from_index` becomes TypeScript `fromIndex`. -7. **`User` methods are auto-exposed as MCP tools.** Application - type methods require explicit `mcp=Tool()`. Use `mcp=False` to - hide a method from the AI. +7. **Every method requires explicit `mcp=`.** Use `mcp=Tool()` + to expose a method as an AI-callable tool (required on all + types, including `User`). Use `mcp=None` to hide it from + the AI. 8. **Application types need `factory=True`** on their `create` Writer method. -9. **`npm install` before second `rbt generate`** — React bindings - need `node_modules` to exist. -10. **Generated React hook:** `use()` — e.g., +9. **Method call signatures: pass request fields as kwargs, + never wrap in a Request object.** This applies to every + call shape — factory constructors, regular Writers/Readers/ + Transactions, and `.schedule().method()` for workflows. + Right shapes: + + - `Type.constructor_method(context, field=val, ...)` + - `Type.ref(id).some_method(context, field=val, ...)` + - `Type.ref(id).schedule().my_workflow(context, field=val, ...)` + - For empty request types, omit fields entirely: + `Type.ref(id).schedule().autoplay(context)`. + + Wrong: + + - `Type.constructor_method(context, request=Type.ConstructorMethodRequest(...))` + - `Type.ref(id).some_method(context, request=Type.SomeMethodRequest(...))` + + The `request=` kwarg raises `TypeError: ... got an unexpected keyword argument 'request'` at runtime. + +10. **`npm install` before second `rbt generate`** — React bindings + need `node_modules` to exist. +11. **Generated React hook:** `use()` — e.g., `useCounter()`, `useInventory()`, etc. -11. **Generated React import path:** +12. **Generated React import path:** `@api//v1/_rbt_react` -12. **Generated Python import path:** +13. **Generated Python import path:** `from .v1._rbt import User, Counter` -13. **Use `--default-config=hmr`** in `.rbtrc` (not `--default=hmr`). -14. **`UI(path="web/ui/")`** — path is relative to project root. -15. **`UI(request=)`** passes config as React component +14. **Use `--default-config=hmr`** in `.rbtrc` (not `--default=hmr`). +15. **`UI(path="web/ui/")`** — path is relative to project root. +16. **`UI(request=)`** passes config as React component props. `UI(request=None)` passes no props. -16. **Register all servicers** in `main.py`: +17. **Register all servicers** in `main.py`: `Application(servicers=[UserServicer, CounterServicer])`. -17. The requests and responses on the frontend are always Zod types +18. The requests and responses on the frontend are always Zod types generated from the Python Models. +19. **Inside a Workflow classmethod, `cls` is the servicer, not the + state class.** To call methods on the running instance, use the + state class imported from `_rbt`: + `await MyType.ref().some_method(context)`. A no-arg `.ref()` + inside a Workflow picks up `state_id` from `WorkflowContext` + automatically. **Do NOT write `cls.ref()`** — it fails with + `TypeError: BaseServicer.ref() missing 1 required positional argument: 'self'`, because `ref` on the BaseServicer + is an instance method, not the state-class factory. `self.ref()` + is also wrong because there is no `self` in a classmethod. +20. **Workflows must be scheduled, not awaited, from a + `TransactionContext`/`WriterContext`/`ReaderContext`.** Only + `ExternalContext` and `WorkflowContext` can `await` a workflow + directly. From a transaction that kicks off a workflow on a + state it just created, use `.schedule()`: + `await MyType.ref(id).schedule().autoplay(context)`. + Writing `await MyType.ref(id).autoplay(context)` from a + transaction raises `TypeError: ... '' is a workflow and must be scheduled from a 'TransactionContext' via `await [...].schedule([...]).(context, [...])``. + See the "Scheduling a Workflow from a Transaction" example + in the Workflow Servicer section. +21. **Nested `Model` fields can't take `default_factory` or + `default`.** Two related rules — both raise `UserPydanticError` + at startup, not at field-construction time, so they look like + runtime errors but are static schema problems: + + - `default_factory=` is only supported for `list` and `dict`. + `Field(tag=N, default_factory=MyModel)` raises + `Field in model uses default_factory which is not supported for type . Only list, dict types can have a default_factory currently.` + - A non-Optional `Model`-typed field also can't take + `default=`, even with an instance: + `Field in model is a non-optional Model type and cannot have a default value. Use Optional for Model types with empty default.` + + The fix is to declare the field optional and construct lazily, + e.g. `preferences: Optional[UserPreferences] = Field(tag=N, default=None)`, + then materialize it inside the servicer (or in a factory + `create` method) when the parent state is first written. + +22. **`.per_workflow()` is implicit; don't write it.** Inside a + workflow, `MyType.ref().read(context)` and + `MyType.ref().write(context, fn)` already pick the right + semantics: `.always()` inside an `until` block, + `.per_iteration()` inside a `context.loop`, and + `.per_workflow()` everywhere else. Only reach for an explicit + `.per_iteration()` (override the default to per-iteration when + _not_ inside a loop) or `.always()` (re-run every time). A + plain `MyType.ref().per_workflow().some_method(context)` adds + nothing beyond `MyType.ref().some_method(context)`. +23. **`.read(context)` only works on the workflow's own + no-argument `MyType.ref()`.** Inside a workflow, + `MyType.ref().read(context)` reads the workflow's own state + via the no-argument `ref()` (picks up `state_id` from + `WorkflowContext`). A foreign read like + `OtherType.ref(other_id).read(context)` raises + `RuntimeError: read() is currently only supported within workflows` — the constraint isn't actually "must be inside + a workflow" (you are) but "must be the workflow's own + no-argument ref." For cross-state reads, call a Reader + method on the target type. The same rule applies to inline + `.write(context, fn)`. + + ```python + # GOOD — workflow's own state. + state = await MyType.ref().read(context) + + # GOOD — cross-state read via a Reader method. + response = await User.ref(user_id).get_history(context) + + # BAD — raises the "only supported within workflows" + # RuntimeError despite being inside one. Use a Reader. + # user_state = await User.ref(user_id).read(context) + ``` ## Update Flow diff --git a/reboot/BUILD.bazel b/reboot/BUILD.bazel index f0b355e3..495c14b0 100644 --- a/reboot/BUILD.bazel +++ b/reboot/BUILD.bazel @@ -480,6 +480,7 @@ py_library( # ship with the Reboot library, even when they aren't themselves # used by the Reboot library. deps = [ + "//reboot/agents/pydantic_ai:python", "//reboot/thirdparty/mailgun:servicers_py", ], ) @@ -495,27 +496,21 @@ py_library( ], deps = [ ":api_py", - ":python_std", - ":python_thirdparty", ":protobuf_py", - ":protoc_gen_reboot_nodejs_py", - ":protoc_gen_reboot_react_py", - ":protoc_gen_reboot_nodejs_boilerplate_py", - ":protoc_gen_reboot_web_py", - "@com_github_reboot_dev_reboot//protoc_gen_mypy_plugin:protoc-gen-mypy", ":protoc_gen_es_with_deps_py", + ":protoc_gen_reboot_nodejs_boilerplate_py", + ":protoc_gen_reboot_nodejs_py", ":protoc_gen_reboot_python_boilerplate_py", ":protoc_gen_reboot_python_py", + ":protoc_gen_reboot_react_py", + ":protoc_gen_reboot_web_py", + ":python_std", + ":python_thirdparty", "//reboot/aio:python", "//reboot/cli:rbt_main_py", "//reboot/mcp:python", "//reboot/nodejs:python", - # TODO: This dependency is excluded from the `//reboot/aio:python` target, in order - # to avoid creating a cycle with the code generated by: - # `//rbt/cloud/v1alpha1/secrets:secrets_py_reboot` - # ... we should likely split out the portion of `//reboot/aio:python` that is actually - # required by the stub code. - "//reboot/aio:secrets_py", + "@com_github_reboot_dev_reboot//protoc_gen_mypy_plugin:protoc-gen-mypy", ], ) @@ -583,6 +578,40 @@ sh_binary( deps = [":force_reinstall_dev_reboot_lib"], ) +# See comment on `force_reinstall_dev_reboot_lib` above re: the +# sh_library/sh_binary indirection. +sh_library( + name = "stage_and_publish_local_lib", + srcs = [ + "stage_and_publish_local.sh", + ], + data = [ + ":reboot.dev", + "//rbt/v1alpha1:reboot-dev-reboot-api", + "//reboot/create-ui:reboot-dev-create-ui", + "//reboot/react:reboot-dev-reboot-react", + "//reboot/web:reboot-dev-reboot-web", + ], +) + +# Stage locally-built Reboot artifacts and publish to Verdaccio. +# Intended to be invoked via `ibazel run` so rebuilds on source +# changes automatically re-stage and re-publish. +sh_binary( + name = "stage_and_publish_local", + srcs = [ + "stage_and_publish_local.sh", + ], + data = [ + ":reboot.dev", + "//rbt/v1alpha1:reboot-dev-reboot-api", + "//reboot/create-ui:reboot-dev-create-ui", + "//reboot/react:reboot-dev-reboot-react", + "//reboot/web:reboot-dev-reboot-web", + ], + deps = [":stage_and_publish_local_lib"], +) + # TODO: This `sh_library` is a necessary indirection until Bazel 6, because a `sh_binary` # does not actually force a `data` attribute to build, but a `sh_library` does. We can # remove the indirection once we upgrade. See https://github.com/bazelbuild/bazel/issues/12348 diff --git a/reboot/README.md b/reboot/README.md index 31f1cc80..b0c37f7b 100644 --- a/reboot/README.md +++ b/reboot/README.md @@ -151,7 +151,7 @@ As part of this release, you will release a new version of the `rbt` CLI. This newer CLI likely requires the use of the latest Cloud APIs. Therefore, we must update the Cloud to its latest version before we release the CLI. -Follow the steps [here](../infrastructure/clusters/README.md#performing-a-release) to release +Follow the steps [here](../../infrastructure/clusters/README.md#performing-a-release) to release the Cloud, all the way to `prod1`, before you continue. ### 3. Releasing PyPI packages, npm packages, and examples diff --git a/reboot/agents/pydantic_ai/BUILD.bazel b/reboot/agents/pydantic_ai/BUILD.bazel new file mode 100644 index 00000000..cdf5666d --- /dev/null +++ b/reboot/agents/pydantic_ai/BUILD.bazel @@ -0,0 +1,90 @@ +load("@rbt_pypi//:requirements.bzl", "requirement") +load("@rules_python//python:defs.bzl", "py_library") + +py_library( + name = "_typing_py", + srcs = ["_typing.py"], + srcs_version = "PY3", + visibility = ["//visibility:public"], + deps = [ + requirement("typing-extensions"), + ], +) + +py_library( + name = "_run_py", + srcs = ["_run.py"], + srcs_version = "PY3", + visibility = ["//visibility:public"], + deps = [ + requirement("pydantic-ai-slim"), + "//reboot/aio:contexts_py", + "//reboot/aio:idempotency_py", + "//reboot/aio:workflows_py", + ], +) + +py_library( + name = "_model_py", + srcs = ["_model.py"], + srcs_version = "PY3", + visibility = ["//visibility:public"], + deps = [ + ":_run_py", + requirement("pydantic-ai-slim"), + "//reboot/aio:workflows_py", + ], +) + +py_library( + name = "_toolset_py", + srcs = ["_toolset.py"], + srcs_version = "PY3", + visibility = ["//visibility:public"], + deps = [ + ":_run_py", + ":_typing_py", + requirement("pydantic-ai-slim"), + "//reboot/aio:idempotency_py", + "//reboot/aio:workflows_py", + ], +) + +py_library( + name = "_agent_py", + srcs = ["_agent.py"], + srcs_version = "PY3", + visibility = ["//visibility:public"], + deps = [ + ":_model_py", + ":_run_py", + ":_toolset_py", + ":_typing_py", + requirement("pydantic-ai-slim"), + "//reboot/aio:contexts_py", + ], +) + +py_library( + name = "__init___py", + srcs = ["__init__.py"], + srcs_version = "PY3", + visibility = ["//visibility:public"], + deps = [ + ":_agent_py", + ":_model_py", + ], +) + +py_library( + name = "python", + visibility = ["//visibility:public"], + deps = [ + ":__init___py", + ":_agent_py", + ":_model_py", + ":_run_py", + ":_toolset_py", + ":_typing_py", + ], +) diff --git a/reboot/agents/pydantic_ai/__init__.py b/reboot/agents/pydantic_ai/__init__.py new file mode 100644 index 00000000..8dfa0094 --- /dev/null +++ b/reboot/agents/pydantic_ai/__init__.py @@ -0,0 +1,9 @@ +from ._agent import Agent, ParallelExecutionMode +from ._toolset import ToolFuncContext, ToolFuncPlain + +__all__ = [ + "Agent", + "ParallelExecutionMode", + "ToolFuncContext", + "ToolFuncPlain", +] diff --git a/reboot/agents/pydantic_ai/_agent.py b/reboot/agents/pydantic_ai/_agent.py new file mode 100644 index 00000000..b66e440b --- /dev/null +++ b/reboot/agents/pydantic_ai/_agent.py @@ -0,0 +1,1271 @@ +from __future__ import annotations + +import pydantic_ai +from ._model import _Model +from ._run import _agent_run +from ._toolset import ( + ToolFuncContext, + ToolFuncPlain, + _make_toolset_wrapper, + _wrap_tool_function, + _WrapperToolset, +) +from ._typing import AgentDepsT, OutputDataT, RunOutputDataT, ToolParams +from collections.abc import AsyncIterator, Callable, Iterator, Sequence +from contextlib import ( + AbstractAsyncContextManager, + asynccontextmanager, + contextmanager, +) +from pydantic.json_schema import GenerateJsonSchema +from pydantic_ai import _utils +from pydantic_ai._agent_graph import EndStrategy, HistoryProcessor +from pydantic_ai._instructions import AgentInstructions +from pydantic_ai._template import TemplateStr +from pydantic_ai.agent.abstract import ( + AbstractAgent, + AgentMetadata, + AgentModelSettings, + EventStreamHandler, +) +from pydantic_ai.agent.wrapper import WrapperAgent +from pydantic_ai.capabilities import AbstractCapability +from pydantic_ai.exceptions import UserError +from pydantic_ai.models.instrumented import InstrumentationSettings +from pydantic_ai.output import OutputSpec +from pydantic_ai.result import StreamedRunResult +from pydantic_ai.run import AgentRun, AgentRunResult, AgentRunResultEvent +from pydantic_ai.tools import ( + AgentBuiltinTool, + ArgsValidatorFunc, + DeferredToolResults, + DocstringFormat, + GenerateToolJsonSchema, + RunContext, + Tool, + ToolFuncEither, + ToolPrepareFunc, + ToolsPrepareFunc, +) +from pydantic_ai.toolsets import AbstractToolset, AgentToolset +from pydantic_ai.toolsets.function import FunctionToolset +from reboot.aio.contexts import WorkflowContext +from typing import Any, Literal, TypeAlias, TypeVar, overload + +# Bounded TypeVar for the "no-real-model-was-passed" sentinels `None` +# and `_utils.Unset` which pydantic_ai uses annoyingly in different +# places. +_NoneOrUnset = TypeVar("_NoneOrUnset", None, _utils.Unset) + +# NOTE: this file is using the `T | None` style for optional types +# instead of `Optional[T]` because that is how pydantic_ai does it and +# a lot of the code here is copied from their repository and we'd like +# to keep it consistent as we update it and keep it in sync with the +# upstream pydantic_ai library. + +# Reboot-allowed values for pydantic_ai's +# `parallel_tool_call_execution_mode`. Excludes pydantic_ai's +# default `'parallel'` because it yields tool-result events in +# *completion* order, which depends on asyncio scheduling and is +# therefore non-deterministic across replays -- on replay every +# memoized tool returns effectively instantly, so consumers +# (`event_stream_handler`, `agent.run_stream_events()`) can see +# events in different orders between the original run and a +# replay. Both `'sequential'` (one tool at a time, in original +# order) and `'parallel_ordered_events'` (concurrent execution, +# events emitted in original order after all tools complete) are +# replay-safe. +ParallelExecutionMode: TypeAlias = Literal[ + "sequential", + "parallel_ordered_events", +] + + +class Agent(WrapperAgent[AgentDepsT, OutputDataT]): + """A durable Pydantic AI `Agent` for use in Reboot workflows. + + Each model call is memoized via `at_least_once` so previously- + completed model calls return their cached `ModelResponse` on + workflow replay instead of re-hitting the provider. + + At the start of every agent run we snapshot the agent's static + `instructions` / `system_prompt` and memoize the snapshot via + `at_least_once`. On replay we log a warning if the current + snapshot differs -- a sign that the agent's static + configuration has changed since the run was first executed and + the memoized model responses may no longer reflect the current + configuration. + + `run_stream` and `run_stream_events` are supported, but the + underlying model call is drained and memoized, so events arrive in + a single batch once the model finishes -- token-by-token streaming + is fundamentally incompatible with deterministic replay, unless + the caller was comfortable with a stream being possibly + interrupted and then restarted (which might be worth the tradeoff + for some applications). + + There are two ways to construct an agent: + + * Directly, passing the same arguments you'd pass to + `pydantic_ai.Agent`: + + ```python + from reboot.agents.pydantic_ai import Agent + + agent = Agent( + "anthropic:claude-sonnet-4-5", + instructions="...", + ) + ``` + + * From an already-constructed `pydantic_ai.Agent` via + `Agent.wrap(existing)`: + + ```python + import pydantic_ai + from reboot.agents.pydantic_ai import Agent + + pydantic_agent = pydantic_ai.Agent(...) + agent = Agent.wrap(pydantic_agent) + ``` + """ + + def __init__( + self, + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | str | None = None, + *, + output_type: OutputSpec[OutputDataT] = str, # type: ignore[assignment] + instructions: AgentInstructions[AgentDepsT] = None, + system_prompt: str | Sequence[str] = (), + deps_type: type[AgentDepsT] = type(None), # type: ignore[assignment] + name: str | None = None, + description: TemplateStr[AgentDepsT] | str | None = None, + model_settings: AgentModelSettings[AgentDepsT] | None = None, + retries: int = 1, + validation_context: ( + Any | Callable[[RunContext[AgentDepsT]], Any] + ) = None, + output_retries: int | None = None, + tools: Sequence[ + Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...] + ] = (), + builtin_tools: Sequence[AgentBuiltinTool[AgentDepsT]] = (), + prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = None, + prepare_output_tools: ToolsPrepareFunc[AgentDepsT] | None = None, + toolsets: Sequence[AgentToolset[AgentDepsT]] | None = None, + defer_model_check: bool = False, + end_strategy: EndStrategy = "early", + instrument: InstrumentationSettings | bool | None = None, + metadata: AgentMetadata[AgentDepsT] | None = None, + history_processors: ( + Sequence[HistoryProcessor[AgentDepsT]] | None + ) = None, + event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, + tool_timeout: float | None = None, + max_concurrency: pydantic_ai.concurrency.AnyConcurrencyLimit = None, + capabilities: Sequence[AbstractCapability[AgentDepsT]] | None = None, + parallel_execution_mode: ParallelExecutionMode = ( + "parallel_ordered_events" + ), + **kwargs: Any, + ) -> None: + # Forward every argument to `pydantic_ai.Agent`. The explicit + # signature here gives IDEs and type checkers the full surface + # area; the `**kwargs` catch-all mirrors upstream, so any + # parameter added to `pydantic_ai.Agent.__init__` in a future + # release still passes through transparently. + wrapped: AbstractAgent[AgentDepsT, OutputDataT] = pydantic_ai.Agent( + model, + output_type=output_type, + instructions=instructions, + system_prompt=system_prompt, + deps_type=deps_type, + name=name, + description=description, + model_settings=model_settings, + retries=retries, + validation_context=validation_context, + output_retries=output_retries, + tools=tools, + builtin_tools=builtin_tools, + prepare_tools=prepare_tools, + prepare_output_tools=prepare_output_tools, + toolsets=toolsets, + defer_model_check=defer_model_check, + end_strategy=end_strategy, + instrument=instrument, + metadata=metadata, + history_processors=history_processors, + event_stream_handler=event_stream_handler, + tool_timeout=tool_timeout, + max_concurrency=max_concurrency, + capabilities=capabilities, + **kwargs, + ) + self._init_from_wrapped( + wrapped, + parallel_execution_mode=parallel_execution_mode, + ) + + @classmethod + def wrap( + cls, + wrapped: AbstractAgent[AgentDepsT, OutputDataT], + *, + parallel_execution_mode: ParallelExecutionMode = ( + "parallel_ordered_events" + ), + ) -> "Agent[AgentDepsT, OutputDataT]": + """Adopt an already-constructed `pydantic_ai.Agent` (or any + `AbstractAgent`) as a Reboot `Agent`. + + Walks the wrapped agent's toolset tree and replaces every + leaf toolset with a `_WrapperToolset` so existing tool + calls flow through our `at_least_once` / + `idempotency_seeds` envelope. Tools registered on this + Reboot `Agent` after `wrap()` via `@agent.tool` / + `@agent.tool_plain` are added to a separate Reboot-owned + `FunctionToolset` (NOT to the underlying agent), and also + participate in the wrapping. + + The wrapped agent itself is left untouched -- we don't + mutate any of its private state. Toolsets are swapped in + per run via `pydantic_ai.Agent.override(...)`. + + Useful when the upstream agent comes from code you don't + control -- e.g. a factory function or third-party library. + For the common case of constructing an agent from scratch, + call `Agent(...)` directly with the same arguments you'd + pass to `pydantic_ai.Agent`. + """ + instance = cls.__new__(cls) + instance._init_from_wrapped( + wrapped, + parallel_execution_mode=parallel_execution_mode, + ) + return instance + + def _init_from_wrapped( + self, + wrapped: AbstractAgent[AgentDepsT, OutputDataT], + *, + parallel_execution_mode: ParallelExecutionMode, + ) -> None: + WrapperAgent.__init__(self, wrapped) + if wrapped.name is None: + raise UserError( + "An agent needs to have a unique `name` in order " + "to be used with Reboot. The name is used to " + "scope the agent's memoized calls." + ) + self._name: str = wrapped.name + + # Reject pydantic_ai's `'parallel'` mode at construction + # since it produces non-deterministic event ordering on + # replay. Accept only the two replay-safe modes; anything + # else (typo, bare `'parallel'`, etc.) gets a clear error. + if parallel_execution_mode not in ( + "sequential", + "parallel_ordered_events", + ): + raise UserError( + f"`parallel_execution_mode='{parallel_execution_mode}'` " + "is not supported on a Reboot `Agent`: only " + "`'sequential'` and `'parallel_ordered_events'` are " + "replay-safe. Pydantic_ai's default `'parallel'` " + "yields tool-result events in completion order, " + "which depends on asyncio scheduling and is " + "therefore non-deterministic across replays." + ) + self._parallel_execution_mode: ParallelExecutionMode = ( + parallel_execution_mode + ) + + # Reboot-owned `FunctionToolset` for tools registered via + # `@agent.tool` / `@agent.tool_plain` AFTER construction. + # Distinct from the wrapped agent's own `_function_toolset` + # so we never mutate the wrapped agent's state. + self._function_toolset: FunctionToolset[AgentDepsT] = ( + FunctionToolset() + ) + + @property + def name(self) -> str | None: + return self._name + + @name.setter + def name(self, value: str | None) -> None: + raise UserError( + "The agent's name cannot be changed after creation; it " + "is part of the memoization key for every model and " + "tool call in this agent's runs, so changing it would " + "silently shift those keys and break replay. To use a " + "different name, construct a new Reboot `Agent` " + "instead." + ) + + @property + def model( + self + ) -> pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | str | None: + # Surface the user's configured model verbatim. Reboot's + # internal `_Model` wrapping happens at run time inside + # the four entry points; this property is for inspection + # of "what did the user configure?" and intentionally does + # not expose our internal wrapper. + return self.wrapped.model + + @property + def toolsets(self) -> Sequence[AbstractToolset[AgentDepsT]]: + # Surface the user's toolsets verbatim, in the same order an + # actual run dispatches through: our Reboot-owned + # `FunctionToolset` (for `@agent.tool` / `@agent.tool_plain` + # registrations) first, then the wrapped agent's combined + # `toolsets` (its own `_function_toolset` + any + # user-supplied `toolsets=`). Reboot's `_WrapperToolset` + # wrapping happens lazily inside the four entry points; + # this property is for inspection ("what tools does this + # agent have?") and intentionally does not expose our + # internal wrappers. + return [self._function_toolset, *self.wrapped.toolsets] + + @overload + def tool( + self, + function: ToolFuncContext[AgentDepsT, ToolParams], + /, + ) -> ToolFuncContext[AgentDepsT, ToolParams]: + ... + + @overload + def tool( + self, + /, + *, + name: str | None = None, + description: str | None = None, + retries: int | None = None, + prepare: ToolPrepareFunc[AgentDepsT] | None = None, + args_validator: ArgsValidatorFunc[AgentDepsT, ToolParams] | + None = None, + docstring_format: DocstringFormat = "auto", + require_parameter_descriptions: bool = False, + schema_generator: type[GenerateJsonSchema] = GenerateToolJsonSchema, + strict: bool | None = None, + sequential: bool = False, + requires_approval: bool = False, + metadata: dict[str, Any] | None = None, + timeout: float | None = None, + defer_loading: bool = False, + include_return_schema: bool | None = None, + ) -> Callable[ + [ToolFuncContext[AgentDepsT, ToolParams]], + ToolFuncContext[AgentDepsT, ToolParams], + ]: + ... + + def tool( + self, + function: Any = None, + /, + **kwargs: Any, + ) -> Any: + """Register a tool whose first parameter is a Reboot + `WorkflowContext` and whose second parameter is a + pydantic_ai `RunContext[Deps]`. + + Mirrors the signature of `pydantic_ai.Agent.tool` -- both + the bare `@agent.tool` and parametrized + `@agent.tool(retries=2)` forms are supported. + + Example: + ```python + from reboot.agents.pydantic_ai import Agent + from pydantic_ai import RunContext + from reboot.aio.contexts import WorkflowContext + + agent = Agent("test", deps_type=int, name="my_agent") + + @agent.tool + async def lookup( + context: WorkflowContext, + run: RunContext[int], + query: str, + ) -> str: + ... + ``` + """ + + def decorator( + function_: ToolFuncContext[AgentDepsT, ToolParams], + ) -> ToolFuncContext[AgentDepsT, ToolParams]: + self._function_toolset.add_function( + _wrap_tool_function(function_), + takes_ctx=True, + **kwargs, + ) + return function_ + + return decorator if function is None else decorator(function) + + @overload + def tool_plain( + self, + function: ToolFuncPlain[ToolParams], # type: ignore[type-arg] + /, + ) -> ToolFuncPlain[ToolParams]: # type: ignore[type-arg] + ... + + @overload + def tool_plain( + self, + /, + *, + name: str | None = None, + description: str | None = None, + retries: int | None = None, + prepare: ToolPrepareFunc[AgentDepsT] | None = None, + args_validator: ArgsValidatorFunc[AgentDepsT, ToolParams] | + None = None, + docstring_format: DocstringFormat = "auto", + require_parameter_descriptions: bool = False, + schema_generator: type[GenerateJsonSchema] = GenerateToolJsonSchema, + strict: bool | None = None, + sequential: bool = False, + requires_approval: bool = False, + metadata: dict[str, Any] | None = None, + timeout: float | None = None, + defer_loading: bool = False, + include_return_schema: bool | None = None, + ) -> Callable[ # type: ignore[type-arg] + [ToolFuncPlain[ToolParams]], + ToolFuncPlain[ToolParams], + ]: + ... + + def tool_plain( + self, + function: Any = None, + /, + **kwargs: Any, + ) -> Any: + """Register a tool that takes neither a Reboot + `WorkflowContext` nor a pydantic_ai `RunContext`. + + Mirrors the signature of `pydantic_ai.Agent.tool_plain` -- + both the bare `@agent.tool_plain` and parametrized + `@agent.tool_plain(retries=2)` forms are supported. + + Example: + ```python + from reboot.agents.pydantic_ai import Agent + + agent = Agent("test", name="my_agent") + + @agent.tool_plain + async def add(x: int, y: int) -> int: + return x + y + ``` + + Tool calls still flow through Reboot's `at_least_once` / + `idempotency_seeds` envelope by virtue of the toolset-level + wrapping; the function itself runs unchanged. + """ + + def decorator( + function_: ToolFuncPlain[ToolParams], # type: ignore[type-arg] + ) -> ToolFuncPlain[ToolParams]: # type: ignore[type-arg] + self._function_toolset.add_function( + function_, + takes_ctx=False, + **kwargs, + ) + return function_ + + return decorator if function is None else decorator(function) + + @staticmethod + def _validate_context( + context: WorkflowContext | str | + Sequence[pydantic_ai.messages.UserContent] | None, + *, + method: Literal["run", "iter", "run_stream", "run_stream_events"], + ) -> WorkflowContext: + """Validate the first positional argument of `run` / `iter` / + `run_stream` / `run_stream_events` is actually a + `WorkflowContext`. `method` is the name of the calling + entry point so the raised `UserError` can name it for + the caller. Raises `UserError` naming the fix; on success + returns the context narrowed to `WorkflowContext` so + callers can use it directly. + """ + if not isinstance(context, WorkflowContext): + raise UserError( + f"`Agent.{method}` requires `context: " + f"WorkflowContext` as its first positional " + f"argument; got `{type(context).__name__}`. If " + f"you're calling through a reference typed as " + f"`pydantic_ai.AbstractAgent` (whose signature " + f"has no `context` parameter), you likely passed " + f"`user_prompt` first by mistake -- the Reboot " + f"`Agent` requires a `WorkflowContext` first." + ) + return context + + @overload + @staticmethod + def _wrap_model( + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | + str, + ) -> pydantic_ai.models.Model: + ... + + @overload + @staticmethod + def _wrap_model( + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | + str | _NoneOrUnset, + ) -> pydantic_ai.models.Model | _NoneOrUnset: + ... + + @staticmethod + def _wrap_model( + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | + str | None | _utils.Unset, + ) -> pydantic_ai.models.Model | None | _utils.Unset: + """Convert a user-supplied model into a Reboot `_Model` + so every `request` / `request_stream` flows through + `at_least_once`. No-op for already-wrapped Reboot + models, and pass-through for the sentinel forms `None` + (the agent's default-model resolution / pydantic_ai's + own no-model error remain in play) and `_utils.Unset` + (used by callers like `Agent.override(...)` that want + to hand pydantic_ai's "user didn't pass anything" + sentinel through unchanged). + + Strings (the `KnownModelName` shortcut form, e.g., + `"anthropic:claude-sonnet-4-5"`) are resolved to a + concrete `pydantic_ai.models.Model` via + `pydantic_ai.models.infer_model(...)` before wrapping -- + without that step we'd end up with a `_Model` holding a + string instead of a real model, and `request(...)` would + explode at runtime. + + The two overloads above let callers pass either a + concrete model (returns `Model`) or the union including + a sentinel (returns `Model | `). The `_NoneOrUnset` constrained TypeVar + preserves the specific sentinel through the call so + callers don't need to narrow before calling. + """ + if model is None or isinstance(model, _utils.Unset): + return model + if isinstance(model, _Model): + return model + return _Model(pydantic_ai.models.infer_model(model)) + + def _wrap_model_or_default( + self, + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | + str | None, + ) -> pydantic_ai.models.Model | None: + """Wrap a per-run `model` argument; fall back to the + wrapped agent's default `model` (also wrapped, to cover + the `Agent.wrap(bare)` case where `bare.model` is not + already a Reboot `Model`). Either is ignored at run time + when the user has set up an `override(model=...)`, since + pydantic_ai's `_get_model` consults `_override_model` + first. + """ + return self._wrap_model(model or self.wrapped.model) + + def _wrap_toolsets( + self, + additional_toolsets: Sequence[AbstractToolset[AgentDepsT]] | + None = None, + ) -> list[AbstractToolset[AgentDepsT]]: + """Build the run-time toolset list with a Reboot wrapper + applied to each leaf (`_MCPWrapperToolset` for MCP leaves, + `_WrapperToolset` otherwise -- see `_make_toolset_wrapper`), + so every tool call -- and every MCP `get_tools` / + `get_instructions` -- flows through Reboot's + `at_least_once` / `idempotency_seeds` envelope. Includes + our Reboot-owned `FunctionToolset` (for `@agent.tool` / + `@agent.tool_plain` registrations) plus the wrapped + agent's currently-configured toolsets. Re-walked per run + so post-construction additions on either side surface at + the next run. + + `additional_toolsets` accepts the user's per-run `toolsets=` + kwarg from the entry points; pydantic_ai treats those as + additive (`additional_toolsets` in `_build_toolset_list`) + but only when no `_override_toolsets` is active -- since + we always install one, we fold the user's run-time + toolsets in here so they aren't silently dropped. + + A single `_make_toolset_wrapper()` callable is reused + across every `visit_and_replace` call below so its + "at most one id-less MCP toolset" check spans the entire + toolset tree -- including any per-run `additional_toolsets`. + """ + wrap = _make_toolset_wrapper() + wrapped = [ + _WrapperToolset(self._function_toolset), + *( + toolset.visit_and_replace(wrap) + for toolset in self.wrapped.toolsets + ), + ] + if additional_toolsets: + wrapped.extend( + toolset.visit_and_replace(wrap) + for toolset in additional_toolsets + ) + return wrapped + + @contextmanager + def override( + self, + *, + name: str | _utils.Unset = _utils.UNSET, + deps: AgentDepsT | _utils.Unset = _utils.UNSET, + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | + str | _utils.Unset = _utils.UNSET, + toolsets: Sequence[AbstractToolset[AgentDepsT]] | + _utils.Unset = _utils.UNSET, + tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] | + _utils.Unset = _utils.UNSET, + instructions: AgentInstructions[AgentDepsT] | + _utils.Unset = _utils.UNSET, + model_settings: AgentModelSettings[AgentDepsT] | + _utils.Unset = _utils.UNSET, + spec: dict[str, Any] | Any = None, + ) -> Iterator[None]: + """Context manager mirroring `pydantic_ai.Agent.override`, + with one Reboot-specific tweak: any `model` override is + passed through `_wrap_model` so it stays memoized via + `at_least_once`. Pass a `pydantic_ai.models.Model` instance + or a `KnownModelName` string -- both are auto-wrapped. + + Overriding `toolsets=` is currently NOT supported on a + Reboot `Agent`. The four entry points install their own + per-run override of `toolsets` (so every tool call flows + through Reboot's `_WrapperToolset`), and pydantic_ai's + ContextVar-based override stack means that inner override + would silently shadow whatever the user supplied here. + """ + if _utils.is_set(toolsets): + raise UserError( + "Overriding `toolsets` on a Reboot `Agent` is not " + "currently supported: the per-run wrapping that " + "memoizes tool calls would shadow the override " + "and your toolsets would be silently dropped. If " + "this is important for your use case, please " + "reach out to the maintainers." + ) + with self.wrapped.override( + name=name, + deps=deps, + model=self._wrap_model(model), + toolsets=toolsets, + tools=tools, + instructions=instructions, + model_settings=model_settings, + spec=spec, + ): + yield + + # `# type: ignore[override]` needed on the first `@overload`: + # because we've added `context: WorkflowContext` as the first + # argument which is incompatible with the supertype's + # `user_prompt` first argument, even though our concrete + # implemntation below correctly widens `context` to be type safe, + # mypy still needs this type supression. + @overload # type: ignore[override] + async def run( + self, + context: WorkflowContext, + user_prompt: str | Sequence[pydantic_ai.messages.UserContent] | + None = None, + *, + output_type: None = None, + message_history: Sequence[pydantic_ai.messages.ModelMessage] | + None = None, + deferred_tool_results: DeferredToolResults | None = None, + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | + str | None = None, + instructions: AgentInstructions[AgentDepsT] = None, + deps: AgentDepsT = None, # type: ignore[assignment] + model_settings: AgentModelSettings[AgentDepsT] | None = None, + usage_limits: pydantic_ai.usage.UsageLimits | None = None, + usage: pydantic_ai.usage.RunUsage | None = None, + metadata: AgentMetadata[AgentDepsT] | None = None, + infer_name: bool = True, + toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AgentBuiltinTool[AgentDepsT]] | None = None, + event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, + spec: dict[str, Any] | Any = None, + variant: str | None = None, + ) -> AgentRunResult[OutputDataT]: + ... + + @overload + async def run( + self, + context: WorkflowContext, + user_prompt: str | Sequence[pydantic_ai.messages.UserContent] | + None = None, + *, + output_type: OutputSpec[RunOutputDataT], + message_history: Sequence[pydantic_ai.messages.ModelMessage] | + None = None, + deferred_tool_results: DeferredToolResults | None = None, + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | + str | None = None, + instructions: AgentInstructions[AgentDepsT] = None, + deps: AgentDepsT = None, # type: ignore[assignment] + model_settings: AgentModelSettings[AgentDepsT] | None = None, + usage_limits: pydantic_ai.usage.UsageLimits | None = None, + usage: pydantic_ai.usage.RunUsage | None = None, + metadata: AgentMetadata[AgentDepsT] | None = None, + infer_name: bool = True, + toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AgentBuiltinTool[AgentDepsT]] | None = None, + event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, + spec: dict[str, Any] | Any = None, + variant: str | None = None, + ) -> AgentRunResult[RunOutputDataT]: + ... + + async def run( + self, + # We always require a `WorkflowContext` here, but to + # override the supertype's signature (which has + # `user_prompt` as the first positional) we have to + # type this permissively. We raise a clear `UserError` + # at runtime if the caller passes something other than + # a `WorkflowContext`. + context: WorkflowContext | str | + Sequence[pydantic_ai.messages.UserContent] | None = None, + user_prompt: str | Sequence[pydantic_ai.messages.UserContent] | + None = None, + *, + output_type: OutputSpec[RunOutputDataT] | None = None, + message_history: Sequence[pydantic_ai.messages.ModelMessage] | + None = None, + deferred_tool_results: DeferredToolResults | None = None, + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | + str | None = None, + instructions: AgentInstructions[AgentDepsT] = None, + deps: AgentDepsT = None, # type: ignore[assignment] + model_settings: AgentModelSettings[AgentDepsT] | None = None, + usage_limits: pydantic_ai.usage.UsageLimits | None = None, + usage: pydantic_ai.usage.RunUsage | None = None, + metadata: AgentMetadata[AgentDepsT] | None = None, + infer_name: bool = True, + toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AgentBuiltinTool[AgentDepsT]] | None = None, + event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, + spec: dict[str, Any] | Any = None, + variant: str | None = None, + ) -> AgentRunResult[Any]: + context = self._validate_context(context, method="run") + wrapped_model = self._wrap_model_or_default(model) + async with _agent_run( + context, + agent_name=self._name, + user_prompt=user_prompt, + variant=variant, + message_history=message_history, + instructions=getattr(self.wrapped, "_instructions", ()), + system_prompts=getattr(self.wrapped, "_system_prompts", ()), + model_id=wrapped_model.model_id if wrapped_model else None, + run_instructions=instructions, + run_toolsets=toolsets, + run_builtin_tools=builtin_tools, + run_model_settings=model_settings, + run_output_type=output_type, + run_deferred_tool_results=deferred_tool_results, + ): + with self.wrapped.override( + # Need to override `toolsets` vs passing them along + # below because passing them along below is treated as + # additive whereas we need a full replacement so every + # tool call dispatches through `_WrapperToolset`. + toolsets=self._wrap_toolsets(additional_toolsets=toolsets), + tools=[], + ), self.wrapped.parallel_tool_call_execution_mode( + self._parallel_execution_mode, + ): + return await self.wrapped.run( + user_prompt, + output_type=output_type, + message_history=message_history, + deferred_tool_results=deferred_tool_results, + # Need to pass `wrapped_model` here vs in + # `self.wrapped.override` above so that if a + # caller has an outer `agent.override(model=...)` + # it will still win (and we'll have already + # wrapped it there). + model=wrapped_model, + instructions=instructions, + deps=deps, + model_settings=model_settings, + usage_limits=usage_limits, + usage=usage, + metadata=metadata, + infer_name=infer_name, + builtin_tools=builtin_tools, + event_stream_handler=event_stream_handler, + spec=spec, + ) + + # See `run`'s @overload above for why the `[override]` + # suppression is needed here. + @overload # type: ignore[override] + def iter( + self, + context: WorkflowContext, + user_prompt: str | Sequence[pydantic_ai.messages.UserContent] | + None = None, + *, + output_type: None = None, + message_history: Sequence[pydantic_ai.messages.ModelMessage] | + None = None, + deferred_tool_results: DeferredToolResults | None = None, + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | + str | None = None, + instructions: AgentInstructions[AgentDepsT] = None, + deps: AgentDepsT = None, # type: ignore[assignment] + model_settings: AgentModelSettings[AgentDepsT] | None = None, + usage_limits: pydantic_ai.usage.UsageLimits | None = None, + usage: pydantic_ai.usage.RunUsage | None = None, + metadata: AgentMetadata[AgentDepsT] | None = None, + infer_name: bool = True, + toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AgentBuiltinTool[AgentDepsT]] | None = None, + spec: dict[str, Any] | Any = None, + variant: str | None = None, + ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: + ... + + @overload + def iter( + self, + context: WorkflowContext, + user_prompt: str | Sequence[pydantic_ai.messages.UserContent] | + None = None, + *, + output_type: OutputSpec[RunOutputDataT], + message_history: Sequence[pydantic_ai.messages.ModelMessage] | + None = None, + deferred_tool_results: DeferredToolResults | None = None, + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | + str | None = None, + instructions: AgentInstructions[AgentDepsT] = None, + deps: AgentDepsT = None, # type: ignore[assignment] + model_settings: AgentModelSettings[AgentDepsT] | None = None, + usage_limits: pydantic_ai.usage.UsageLimits | None = None, + usage: pydantic_ai.usage.RunUsage | None = None, + metadata: AgentMetadata[AgentDepsT] | None = None, + infer_name: bool = True, + toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AgentBuiltinTool[AgentDepsT]] | None = None, + spec: dict[str, Any] | Any = None, + variant: str | None = None, + ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: + ... + + @asynccontextmanager + async def iter( + self, + # We always require a `WorkflowContext` here, but to + # override the supertype's signature (which has + # `user_prompt` as the first positional) we have to + # type this permissively. We raise a clear `UserError` + # at runtime if the caller passes something other than + # a `WorkflowContext`. + context: WorkflowContext | str | + Sequence[pydantic_ai.messages.UserContent] | None = None, + user_prompt: str | Sequence[pydantic_ai.messages.UserContent] | None = None, + *, + output_type: OutputSpec[RunOutputDataT] | None = None, + message_history: Sequence[pydantic_ai.messages.ModelMessage] | None = None, + deferred_tool_results: DeferredToolResults | None = None, + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | str | None = None, + instructions: AgentInstructions[AgentDepsT] = None, + deps: AgentDepsT = None, # type: ignore[assignment] + model_settings: AgentModelSettings[AgentDepsT] | None = None, + usage_limits: pydantic_ai.usage.UsageLimits | None = None, + usage: pydantic_ai.usage.RunUsage | None = None, + metadata: AgentMetadata[AgentDepsT] | None = None, + infer_name: bool = True, + toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AgentBuiltinTool[AgentDepsT]] | None = None, + spec: dict[str, Any] | Any = None, + variant: str | None = None, + ) -> AsyncIterator[AgentRun[AgentDepsT, Any]]: + context = self._validate_context(context, method="iter") + wrapped_model = self._wrap_model_or_default(model) + async with _agent_run( + context, + agent_name=self._name, + user_prompt=user_prompt, + variant=variant, + message_history=message_history, + instructions=getattr(self.wrapped, "_instructions", ()), + system_prompts=getattr(self.wrapped, "_system_prompts", ()), + model_id=wrapped_model.model_id if wrapped_model else None, + run_instructions=instructions, + run_toolsets=toolsets, + run_builtin_tools=builtin_tools, + run_model_settings=model_settings, + run_output_type=output_type, + run_deferred_tool_results=deferred_tool_results, + ): + with self.wrapped.override( + # Need to override `toolsets` vs passing them along + # below because passing them along below is treated as + # additive whereas we need a full replacement so every + # tool call dispatches through `_WrapperToolset`. + toolsets=self._wrap_toolsets(additional_toolsets=toolsets), + tools=[], + ), self.wrapped.parallel_tool_call_execution_mode( + self._parallel_execution_mode, + ): + async with self.wrapped.iter( + user_prompt, + output_type=output_type, + message_history=message_history, + deferred_tool_results=deferred_tool_results, + # Need to pass `wrapped_model` here vs in + # `self.wrapped.override` above so that if a + # caller has an outer `agent.override(model=...)` + # it will still win (and we'll have already + # wrapped it there). + model=wrapped_model, + instructions=instructions, + deps=deps, + model_settings=model_settings, + usage_limits=usage_limits, + usage=usage, + metadata=metadata, + infer_name=infer_name, + builtin_tools=builtin_tools, + spec=spec, + ) as run: + yield run + + # See `run`'s @overload above for why the `[override]` + # suppression is needed here. + @overload # type: ignore[override] + def run_stream( + self, + context: WorkflowContext, + user_prompt: str | Sequence[pydantic_ai.messages.UserContent] | + None = None, + *, + output_type: None = None, + message_history: Sequence[pydantic_ai.messages.ModelMessage] | + None = None, + deferred_tool_results: DeferredToolResults | None = None, + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | + str | None = None, + instructions: AgentInstructions[AgentDepsT] = None, + deps: AgentDepsT = None, # type: ignore[assignment] + model_settings: AgentModelSettings[AgentDepsT] | None = None, + usage_limits: pydantic_ai.usage.UsageLimits | None = None, + usage: pydantic_ai.usage.RunUsage | None = None, + metadata: AgentMetadata[AgentDepsT] | None = None, + infer_name: bool = True, + toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AgentBuiltinTool[AgentDepsT]] | None = None, + event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, + spec: dict[str, Any] | Any = None, + variant: str | None = None, + ) -> AbstractAsyncContextManager[StreamedRunResult[AgentDepsT, OutputDataT] + ]: + ... + + @overload + def run_stream( + self, + context: WorkflowContext, + user_prompt: str | Sequence[pydantic_ai.messages.UserContent] | + None = None, + *, + output_type: OutputSpec[RunOutputDataT], + message_history: Sequence[pydantic_ai.messages.ModelMessage] | + None = None, + deferred_tool_results: DeferredToolResults | None = None, + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | + str | None = None, + instructions: AgentInstructions[AgentDepsT] = None, + deps: AgentDepsT = None, # type: ignore[assignment] + model_settings: AgentModelSettings[AgentDepsT] | None = None, + usage_limits: pydantic_ai.usage.UsageLimits | None = None, + usage: pydantic_ai.usage.RunUsage | None = None, + metadata: AgentMetadata[AgentDepsT] | None = None, + infer_name: bool = True, + toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AgentBuiltinTool[AgentDepsT]] | None = None, + event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, + spec: dict[str, Any] | Any = None, + variant: str | None = None, + ) -> AbstractAsyncContextManager[StreamedRunResult[AgentDepsT, + RunOutputDataT]]: + ... + + @asynccontextmanager + async def run_stream( + self, + # We always require a `WorkflowContext` here, but to + # override the supertype's signature (which has + # `user_prompt` as the first positional) we have to + # type this permissively. We raise a clear `UserError` + # at runtime if the caller passes something other than + # a `WorkflowContext`. + context: WorkflowContext | str | + Sequence[pydantic_ai.messages.UserContent] | None = None, + user_prompt: str | Sequence[pydantic_ai.messages.UserContent] | None = None, + *, + output_type: OutputSpec[RunOutputDataT] | None = None, + message_history: Sequence[pydantic_ai.messages.ModelMessage] | None = None, + deferred_tool_results: DeferredToolResults | None = None, + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | str | None = None, + instructions: AgentInstructions[AgentDepsT] = None, + deps: AgentDepsT = None, # type: ignore[assignment] + model_settings: AgentModelSettings[AgentDepsT] | None = None, + usage_limits: pydantic_ai.usage.UsageLimits | None = None, + usage: pydantic_ai.usage.RunUsage | None = None, + metadata: AgentMetadata[AgentDepsT] | None = None, + infer_name: bool = True, + toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AgentBuiltinTool[AgentDepsT]] | None = None, + event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, + spec: dict[str, Any] | Any = None, + variant: str | None = None, + ) -> AsyncIterator[StreamedRunResult[AgentDepsT, Any]]: + context = self._validate_context(context, method="run_stream") + wrapped_model = self._wrap_model_or_default(model) + async with _agent_run( + context, + agent_name=self._name, + user_prompt=user_prompt, + variant=variant, + message_history=message_history, + instructions=getattr(self.wrapped, "_instructions", ()), + system_prompts=getattr(self.wrapped, "_system_prompts", ()), + model_id=wrapped_model.model_id if wrapped_model else None, + run_instructions=instructions, + run_toolsets=toolsets, + run_builtin_tools=builtin_tools, + run_model_settings=model_settings, + run_output_type=output_type, + run_deferred_tool_results=deferred_tool_results, + ): + with self.wrapped.override( + # Need to override `toolsets` vs passing them along + # below because passing them along below is treated as + # additive whereas we need a full replacement so every + # tool call dispatches through `_WrapperToolset`. + toolsets=self._wrap_toolsets(additional_toolsets=toolsets), + tools=[], + ), self.wrapped.parallel_tool_call_execution_mode( + self._parallel_execution_mode, + ): + async with self.wrapped.run_stream( + user_prompt, + output_type=output_type, + message_history=message_history, + deferred_tool_results=deferred_tool_results, + # Need to pass `wrapped_model` here vs in + # `self.wrapped.override` above so that if a + # caller has an outer `agent.override(model=...)` + # it will still win (and we'll have already + # wrapped it there). + model=wrapped_model, + instructions=instructions, + deps=deps, + model_settings=model_settings, + usage_limits=usage_limits, + usage=usage, + metadata=metadata, + infer_name=infer_name, + builtin_tools=builtin_tools, + event_stream_handler=event_stream_handler, + spec=spec, + ) as stream_result: + # The underlying model call goes through + # `Model.request_stream`, so the live stream is fully + # drained inside an `at_least_once` block and the + # final `ModelResponse` is memoized. The + # `StreamedRunResult` yielded here is backed by a + # `CompletedStreamedResponse`: iterating it returns + # the cached response all at once instead of + # token-by-token. + yield stream_result + + # See `run`'s @overload above for why the `[override]` + # suppression is needed here. + @overload # type: ignore[override] + def run_stream_events( + self, + context: WorkflowContext, + user_prompt: str | Sequence[pydantic_ai.messages.UserContent] | + None = None, + *, + output_type: None = None, + message_history: Sequence[pydantic_ai.messages.ModelMessage] | + None = None, + deferred_tool_results: DeferredToolResults | None = None, + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | + str | None = None, + instructions: AgentInstructions[AgentDepsT] = None, + deps: AgentDepsT = None, # type: ignore[assignment] + model_settings: AgentModelSettings[AgentDepsT] | None = None, + usage_limits: pydantic_ai.usage.UsageLimits | None = None, + usage: pydantic_ai.usage.RunUsage | None = None, + metadata: AgentMetadata[AgentDepsT] | None = None, + infer_name: bool = True, + toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AgentBuiltinTool[AgentDepsT]] | None = None, + spec: dict[str, Any] | Any = None, + variant: str | None = None, + ) -> AsyncIterator[pydantic_ai.messages.AgentStreamEvent | + AgentRunResultEvent[OutputDataT]]: + ... + + @overload + def run_stream_events( + self, + context: WorkflowContext, + user_prompt: str | Sequence[pydantic_ai.messages.UserContent] | + None = None, + *, + output_type: OutputSpec[RunOutputDataT], + message_history: Sequence[pydantic_ai.messages.ModelMessage] | + None = None, + deferred_tool_results: DeferredToolResults | None = None, + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | + str | None = None, + instructions: AgentInstructions[AgentDepsT] = None, + deps: AgentDepsT = None, # type: ignore[assignment] + model_settings: AgentModelSettings[AgentDepsT] | None = None, + usage_limits: pydantic_ai.usage.UsageLimits | None = None, + usage: pydantic_ai.usage.RunUsage | None = None, + metadata: AgentMetadata[AgentDepsT] | None = None, + infer_name: bool = True, + toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AgentBuiltinTool[AgentDepsT]] | None = None, + spec: dict[str, Any] | Any = None, + variant: str | None = None, + ) -> AsyncIterator[pydantic_ai.messages.AgentStreamEvent | + AgentRunResultEvent[RunOutputDataT]]: + ... + + async def run_stream_events( + self, + # We always require a `WorkflowContext` here, but to + # override the supertype's signature (which has + # `user_prompt` as the first positional) we have to + # type this permissively. We raise a clear `UserError` + # at runtime if the caller passes something other than + # a `WorkflowContext`. + context: WorkflowContext | str | + Sequence[pydantic_ai.messages.UserContent] | None = None, + user_prompt: str | Sequence[pydantic_ai.messages.UserContent] | None = None, + *, + output_type: OutputSpec[RunOutputDataT] | None = None, + message_history: Sequence[pydantic_ai.messages.ModelMessage] | None = None, + deferred_tool_results: DeferredToolResults | None = None, + model: pydantic_ai.models.Model | pydantic_ai.models.KnownModelName | str | None = None, + instructions: AgentInstructions[AgentDepsT] = None, + deps: AgentDepsT = None, # type: ignore[assignment] + model_settings: AgentModelSettings[AgentDepsT] | None = None, + usage_limits: pydantic_ai.usage.UsageLimits | None = None, + usage: pydantic_ai.usage.RunUsage | None = None, + metadata: AgentMetadata[AgentDepsT] | None = None, + infer_name: bool = True, + toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AgentBuiltinTool[AgentDepsT]] | None = None, + spec: dict[str, Any] | Any = None, + variant: str | None = None, + ) -> AsyncIterator[pydantic_ai.messages.AgentStreamEvent | AgentRunResultEvent[Any]]: + context = self._validate_context(context, method="run_stream_events") + wrapped_model = self._wrap_model_or_default(model) + async with _agent_run( + context, + agent_name=self._name, + user_prompt=user_prompt, + variant=variant, + message_history=message_history, + instructions=getattr(self.wrapped, "_instructions", ()), + system_prompts=getattr(self.wrapped, "_system_prompts", ()), + model_id=wrapped_model.model_id if wrapped_model else None, + run_instructions=instructions, + run_toolsets=toolsets, + run_builtin_tools=builtin_tools, + run_model_settings=model_settings, + run_output_type=output_type, + run_deferred_tool_results=deferred_tool_results, + ): + with self.wrapped.override( + # Need to override `toolsets` vs passing them along + # below because passing them along below is treated as + # additive whereas we need a full replacement so every + # tool call dispatches through `_WrapperToolset`. + toolsets=self._wrap_toolsets(additional_toolsets=toolsets), + tools=[], + ), self.wrapped.parallel_tool_call_execution_mode( + self._parallel_execution_mode, + ): + async for event in self.wrapped.run_stream_events( + user_prompt, + output_type=output_type, + message_history=message_history, + deferred_tool_results=deferred_tool_results, + # Need to pass `wrapped_model` here vs in + # `self.wrapped.override` above so that if a + # caller has an outer `agent.override(model=...)` + # it will still win (and we'll have already + # wrapped it there). + model=wrapped_model, + instructions=instructions, + deps=deps, + model_settings=model_settings, + usage_limits=usage_limits, + usage=usage, + metadata=metadata, + infer_name=infer_name, + builtin_tools=builtin_tools, + spec=spec, + ): + yield event + + # We're not bothering to keep all of args and kwargs in sync so we + # need this type supression. + def run_sync( # type: ignore[override] + self, + *args: Any, + **kwargs: Any, + ) -> AgentRunResult[Any]: + raise UserError( + "`Agent.run_sync` is not supported: a " + "Reboot workflow method is always async, so call " + "`await agent.run(context, ...)` instead." + ) + + # We're not bothering to keep all of args and kwargs in sync so we + # need this type supression. + def run_stream_sync( # type: ignore[override] + self, + *args: Any, + **kwargs: Any, + ) -> Any: + raise UserError( + "`Agent.run_stream_sync` is not " + "supported: a Reboot workflow method is always async, " + "so use `async with agent.run_stream(context, ...) as " + "result:` instead." + ) diff --git a/reboot/agents/pydantic_ai/_model.py b/reboot/agents/pydantic_ai/_model.py new file mode 100644 index 00000000..b3cea166 --- /dev/null +++ b/reboot/agents/pydantic_ai/_model.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import pydantic_ai +from ._run import _current_run_step, _require_workflow_context +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager +from pydantic_ai._run_context import RunContext +from pydantic_ai.messages import ModelMessage, ModelResponse +from pydantic_ai.models import ModelRequestParameters, StreamedResponse +from pydantic_ai.models.wrapper import CompletedStreamedResponse, WrapperModel +from pydantic_ai.settings import ModelSettings +from reboot.aio.workflows import at_least_once +from typing import Any + +# NOTE: this file is using the `T | None` style for optional types +# instead of `Optional[T]` because that is how pydantic_ai does it and +# a lot of the code here is copied from their repository and we'd like +# to keep it consistent as we update it and keep it in sync with the +# upstream pydantic_ai library. + + +class _Model(WrapperModel): + """An internal `pydantic_ai.models.Model` wrapper -- never + exposed to users; the Reboot `Agent` constructs and installs + one per run -- that runs every + `request()` through Reboot's `at_least_once`, so that + previously-completed model calls return their cached + `ModelResponse` on workflow replay instead of re-hitting the + provider. + + `request_stream()` is also memoized: the stream is fully + consumed inside the memoized block and only the final + `ModelResponse` is cached. On replay the context manager yields + a `CompletedStreamedResponse` backed by the cached response; + iterating it returns the cached response in a single batch + rather than token-by-token -- live streaming is fundamentally + incompatible with deterministic replay. + """ + + def __init__(self, wrapped: pydantic_ai.models.Model) -> None: + super().__init__(wrapped) + + @staticmethod + def _make_alias() -> str: + # NOTE: alias must match between `request` and + # `request_stream` so that a programmer switching between + # `Agent.run` / `Agent.iter` and `Agent.run_stream` / + # `Agent.run_stream_events` on replay still gets any + # memoized `ModelResponse` instead of re-calling the + # provider. + return f"Model request for step #{_current_run_step()}" + + async def request( + self, + messages: list[ModelMessage], + model_settings: ModelSettings | None, + model_request_parameters: ModelRequestParameters, + ) -> ModelResponse: + context = _require_workflow_context() + + async def call() -> ModelResponse: + return await self.wrapped.request( + messages, + model_settings, + model_request_parameters, + ) + + return await at_least_once( + self._make_alias(), + context, + call, + ) + + @asynccontextmanager + async def request_stream( + self, + messages: list[ModelMessage], + model_settings: ModelSettings | None, + model_request_parameters: ModelRequestParameters, + run_context: RunContext[Any] | None = None, + ) -> AsyncIterator[StreamedResponse]: + context = _require_workflow_context() + + async def call() -> ModelResponse: + async with self.wrapped.request_stream( + messages, + model_settings, + model_request_parameters, + run_context, + ) as stream: + # Drain the stream so the provider finishes producing + # and we can memoize a completed response. + async for _ in stream: + pass + return stream.get() + + response = await at_least_once( + self._make_alias(), + context, + call, + ) + + yield CompletedStreamedResponse( + model_request_parameters=model_request_parameters, + response=response, + ) diff --git a/reboot/agents/pydantic_ai/_run.py b/reboot/agents/pydantic_ai/_run.py new file mode 100644 index 00000000..ea357b1e --- /dev/null +++ b/reboot/agents/pydantic_ai/_run.py @@ -0,0 +1,489 @@ +from __future__ import annotations + +import dataclasses +import hashlib +import logging +import pickle +import uuid +from collections.abc import AsyncIterator, Sequence +from contextlib import asynccontextmanager +from contextvars import ContextVar +from pydantic_ai._run_context import get_current_run_context +from pydantic_ai.exceptions import UserError +from pydantic_ai.messages import ModelMessage, UserContent +from reboot.aio.contexts import WorkflowContext +from reboot.aio.workflows import at_least_once +from typing import Any, Optional + +# NOTE: this file is using the `T | None` style for optional types +# instead of `Optional[T]` because that is how pydantic_ai does it and +# a lot of the code here is copied from their repository and we'd like +# to keep it consistent as we update it and keep it in sync with the +# upstream pydantic_ai library. + +logger = logging.getLogger(__name__) + +# The active `WorkflowContext` for the current agent run. Stored in +# a `ContextVar` so the wrapped pydantic_ai stack can pick it up +# from inside `Model.request` without explicit plumbing through +# every model call. The model-call sequence number ("step") is +# read directly from `pydantic_ai.get_current_run_context().run_step`, +# which pydantic_ai bumps once per turn before each model request, +# so we don't keep our own counter. +_workflow_context: ContextVar[Optional[WorkflowContext]] = ContextVar( + "`WorkflowContext` for the current agent run", + default=None, +) + + +def _require_workflow_context() -> WorkflowContext: + context = _workflow_context.get() + if context is None: + raise UserError( + "Did you forget to call `Agent.run(context, ...)`, " + "`Agent.iter(context, ...)`, `Agent.run_stream(context, " + "...)`, or `Agent.run_stream_events(context, ...)`?" + ) + return context + + +def _current_run_step() -> int: + """Return the `run_step` of the active pydantic_ai `RunContext`. + + pydantic_ai installs the active `RunContext` via a `ContextVar` + before every `Model.request` / `Model.request_stream` call (see + `pydantic_ai._agent_graph.ModelRequestNode._make_request`), and + bumps `run_step` exactly once per turn inside + `_prepare_request`. Use it as the per-turn sequence number for + memoization aliases so successive operations in a single agent + run memoize under distinct keys. + """ + run = get_current_run_context() + if run is None: + raise UserError( + "No active pydantic_ai `RunContext`: this helper must " + "be called from within an agent run (e.g., `Model.request`, " + "a tool function, or a dynamic instruction callable)." + ) + return run.run_step + + +@dataclasses.dataclass +class _AgentRuns: + """The set of `(name, user_prompt, variant, message_history)` + tuples we've already invoked in a single workflow attempt, split + by scope. Used to refuse duplicate `Agent.run` calls (sequential + or concurrent). + + `per_workflow` covers calls made directly in the workflow method + body, outside any control loop. Entries live until the workflow + method completes. + + `per_iteration` covers calls made inside the current control- + loop iteration. Only one iteration is ever executing at a time + on a given workflow context, so a single set is sufficient -- + when an iteration completes, we just clear the set. + """ + per_workflow: set[tuple[Any, ...]] = dataclasses.field( + default_factory=set, + ) + per_iteration: set[tuple[Any, ...]] = dataclasses.field( + default_factory=set, + ) + + +# Per-workflow tracking dict, populated lazily on first +# `_agent_run` entry for a given `WorkflowContext`. Cleaned up via +# two callbacks registered on the `WorkflowContext` the first time +# `_agent_run` is entered for it: +# +# - `on_iteration_complete`: clears the `per_iteration` set so a +# long-running loop's per-iteration tracking doesn't accumulate. +# +# - `on_method_complete`: drops the entire workflow entry when the +# workflow method finishes (succeeded or raised). +_agent_runs: dict[uuid.UUID, _AgentRuns] = {} + + +@dataclasses.dataclass(kw_only=True) +class _Digests: + """Snapshot of an agent run's configuration captured at the + start of the run, memoized via `at_least_once` so we can warn + on replay when any field has changed. Each field is an opaque + digest string except `model_id`, which is the human-readable + identifier (e.g. `"anthropic:claude-sonnet-4-5"`) so warnings + can name both the original and current models. + + Stored as a dataclass rather than a tuple so we can add or + remove fields in the future without breaking the + `at_least_once` slot's pickle compatibility: + + - **Adding a field** is safe. `pickle` restores instances by + writing the saved `__dict__` directly, so an old memoized + `_Digests` deserializes without the new attribute. The + comparison loop reads via + `getattr(memoized, field, None)`, so a missing field + surfaces as `None` and is skipped (no false-positive + warning). + - **Removing a field** is safe. The dropped attribute is + restored onto the instance from the pickle payload but is + simply ignored: the comparison loop iterates + `_DIGEST_FIELD_LABELS`, which we keep in sync with the + class definition, so absent labels mean absent checks. + + `kw_only=True` keeps construction call-sites stable as fields + come and go: every site uses keyword arguments, so adding / + reordering fields can never silently shift positional + bindings. + """ + # Captured from the agent's static configuration. + instructions: str + system_prompts: str + model_id: str + # Captured from the per-call kwargs to `Agent.run` / `iter` / + # `run_stream` / `run_stream_events`. + run_instructions: str + run_toolsets: str + run_builtin_tools: str + run_model_settings: str + run_output_type: str + run_deferred_tool_results: str + + +# `(field_name, human-readable label)` pairs used by +# `_agent_run` to produce targeted warnings when individual +# `_Digests` fields diverge between the original run and a +# replay. New fields added to `_Digests` should also be added +# here. +_DIGEST_FIELD_LABELS: tuple[tuple[str, str], ...] = ( + ("instructions", "static instructions"), + ("system_prompts", "static system prompts"), + ("model_id", "model"), + ("run_instructions", "the `instructions=` argument"), + ("run_toolsets", "the `toolsets=` argument"), + ("run_builtin_tools", "the `builtin_tools=` argument"), + ("run_model_settings", "the `model_settings=` argument"), + ("run_output_type", "the `output_type=` argument"), + ("run_deferred_tool_results", "the `deferred_tool_results=` argument"), +) + + +def _digest(value: Any, *, hint: str) -> str: + """Return a stable digest of `value`. We use `pickle` not `repr` + because the digest needs to be stable across processes (so replays + in a different process can match the original run) and `repr` of + arbitrary objects may embed memory addresses. + + This is best-effort only: we're expecting to only see values that + don't change how they pickle from one run to the next. If pickling + fails we re-raise as `UserError` with `hint` -- a caller-supplied, + field-specific sentence pointing the user at the most likely fix + -- so the runtime error names the problematic field rather than + surfacing a bare `AttributeError: Can't pickle local object`. + """ + try: + # Pinned `protocol=4` so the digest is stable across Python + # upgrades that might change `pickle.DEFAULT_PROTOCOL`. + data = pickle.dumps(value, protocol=4) + except Exception as exception: + raise UserError( + f"Could not compute a stable digest for the agent run: {hint} " + f"(from {type(exception).__name__}: {exception})" + ) from exception + return hashlib.blake2b(data).hexdigest() + + +def _as_hashable(value: Any) -> Any: + """Normalise a value to something usable as a `set` key. + + Lists and pydantic-ai message dataclasses aren't hashable; we + fall back to `repr(...)` for those. Strings, scalars, and + `None` pass through unchanged so the key preserves the + distinction between "caller omitted" (`None`) and any actual + value. + + This only needs to be deterministically hashable through the + lifetime of a single process, which `repr(...)` satisifes. + """ + if value is None or isinstance(value, (str, int, float, bool)): + return value + return repr(value) + + +@asynccontextmanager +async def _agent_run( + context: WorkflowContext, + *, + agent_name: str, + user_prompt: str | Sequence[UserContent] | None, + variant: str | None, + message_history: Sequence[ModelMessage] | None, + instructions: Any, + system_prompts: Any, + model_id: str | None, + run_instructions: Any, + run_toolsets: Any, + run_builtin_tools: Any, + run_model_settings: Any, + run_output_type: Any, + run_deferred_tool_results: Any, +) -> AsyncIterator[None]: + """Install the active `WorkflowContext` for the duration of an + `Agent.run` / `iter` / `run_stream` / `run_stream_events` call. + + Validates `user_prompt` and `variant` are not the empty string + (which is almost always a programming error and is confusingly + indistinguishable from a deliberately-empty alias slot in logs + and traces). + + Allocates the per-workflow bucket in `_agent_runs` (and + registers lifecycle callbacks on the `WorkflowContext` so the + bucket gets cleaned up at iteration / method boundaries) on + first encounter of a given workflow, then refuses duplicate + `(agent_name, user_prompt, variant, message_history)` tuples in + the same workflow / iteration -- both sequential and concurrent + via `asyncio.gather`. The check-and-add is performed before any + `await` so it is atomic under asyncio's cooperative scheduling. + + Opens a `context.idempotency_seeds(...)` block so every + `Idempotency`-keyed call inside the agent run -- our own model + requests *and* any `at_least_once` / idempotent method calls + inside tools or dynamic instruction functions -- picks up the + agent run's scope on its alias. + + Snapshots the agent's static `instructions` and `system_prompts` + once at the start of the run via `at_least_once`. On replay we + compare the memoized snapshot to the current one and log a + warning if they differ -- a sign that the agent's static + configuration has changed since the run was first executed and + any previously memoized model responses may no longer reflect + the current configuration. + """ + if user_prompt == "": + raise UserError( + "`user_prompt` must not be an empty string; pass " + "`None` (or omit the argument) if you want no user " + "prompt." + ) + if variant == "": + raise UserError( + "`variant` must not be an empty string; pass `None` " + "(or omit the argument) instead." + ) + + if _workflow_context.get() is not None: + raise UserError( + "Nested agent runs are not supported: " + "an `Agent.run` / `iter` / `run_stream` / " + "`run_stream_events` call is already active." + ) + + workflow_id = context.workflow_id + assert workflow_id is not None + + # Get or create the `_agent_runs` entry for this `workflow_id`. + runs = _agent_runs.get(workflow_id) + if runs is None: + runs = _AgentRuns() + _agent_runs[workflow_id] = runs + + def clear_per_iteration(*, iteration: int) -> None: + runs = _agent_runs.get(workflow_id) + assert runs is not None + runs.per_iteration.clear() + + def delete(*, retrying: bool) -> None: + # Using `del` to assert the invariant that we should have + # an entry in `_agent_runs`. + del _agent_runs[workflow_id] + + context.on_iteration_complete(clear_per_iteration) + context.on_method_complete(delete) + + # Now ensure this run is unique. + if context.workflow_iteration is not None: + scope = runs.per_iteration + scope_description = "control-loop iteration" + else: + scope = runs.per_workflow + scope_description = "workflow method" + + run = ( + agent_name, + _as_hashable(user_prompt), + _as_hashable(variant), + _as_hashable(message_history), + ) + + if run in scope: + raise UserError( + f"Duplicate agent run: `{agent_name}` has already been " + "invoked with this `user_prompt`, `variant`, and " + f"`message_history` in the current {scope_description}. " + "Pass a distinct `variant=` to differentiate parallel or " + "repeated calls, or change the `user_prompt` / " + "`message_history`." + ) + + scope.add(run) + + _workflow_context.set(context) + + try: + with context.idempotency_seeds( + { + "agent_name": agent_name, + "agent_run_user_prompt": user_prompt, + "agent_run_variant": variant, + "agent_run_message_history": message_history, + } + ): + # Take a single snapshot of the agent's configuration + # at the start of the run -- both the agent's static + # config and the per-call kwargs the user passed to + # `Agent.run` / `iter` / `run_stream` / + # `run_stream_events`. On replay any divergent field + # produces a targeted warning identifying exactly what + # changed. + # + # Each warning reflects a possible source of + # non-determinism on replay. We've chosen to emit + # warnings rather than raise so users can iterate on + # their agents without breaking in-flight workflows; + # if stricter behavior is wanted later we can hide + # this behind a flag. + digests = _Digests( + # `instructions` and `run_instructions` mix strings + # and callables in a single list, so we filter to + # strings before digesting. + instructions=_digest( + tuple( + instruction for instruction in (instructions or ()) + if isinstance(instruction, str) + ), + hint=( + "the static `instructions=` you passed to " + "`Agent(...)` must be picklable -- only " + "string entries are digested, but at least " + "one string contained an unpicklable value." + ), + ), + # `system_prompts` is already strings-only + # (pydantic_ai keeps callables in + # `_system_prompt_functions` / + # `_system_prompt_dynamic_functions`). + system_prompts=_digest( + tuple(system_prompts or ()), + hint=( + "the `system_prompt=` you passed to " + "`Agent(...)` must be picklable strings." + ), + ), + model_id=model_id or "", + run_instructions=_digest( + tuple( + instruction + for instruction in (run_instructions or ()) + if isinstance(instruction, str) + ), + hint=( + "the per-run `instructions=` you passed to " + "`Agent.run` (or `iter` / `run_stream` / " + "`run_stream_events`) must be picklable -- " + "only string entries are digested, but at " + "least one string contained an unpicklable " + "value." + ), + ), + run_toolsets=_digest( + run_toolsets, + hint=( + "the per-run `toolsets=` you passed to " + "`Agent.run` (or `iter` / `run_stream` / " + "`run_stream_events`) must be picklable. " + "If you passed a `FunctionToolset(tools=" + "[fn])`, define `fn` at module scope -- " + "local functions / closures can't be " + "pickled and would break cross-process " + "digest stability." + ), + ), + run_builtin_tools=_digest( + run_builtin_tools, + hint=( + "the per-run `builtin_tools=` you passed " + "to `Agent.run` (or `iter` / `run_stream` " + "/ `run_stream_events`) must be picklable." + ), + ), + run_model_settings=_digest( + run_model_settings, + hint=( + "the per-run `model_settings=` you passed " + "to `Agent.run` (or `iter` / `run_stream` " + "/ `run_stream_events`) must be picklable." + ), + ), + run_output_type=_digest( + run_output_type, + hint=( + "the per-run `output_type=` you passed to " + "`Agent.run` (or `iter` / `run_stream` / " + "`run_stream_events`) must be picklable. " + "Use a class defined at module scope." + ), + ), + run_deferred_tool_results=_digest( + run_deferred_tool_results, + hint=( + "the per-run `deferred_tool_results=` you " + "passed to `Agent.run` (or `iter` / " + "`run_stream` / `run_stream_events`) must " + "be picklable." + ), + ), + ) + + async def snapshot() -> _Digests: + return digests + + memoized_digests = await at_least_once( + "Snapshot of the agent run configuration", + context, + snapshot, + type=_Digests, + ) + + # Compare field-by-field. `getattr()` tolerates a memoized + # snapshot that pre-dates a newly-added field (forward + # compatibility): a missing field reads as `None`, which + # we skip. + for field_name, label in _DIGEST_FIELD_LABELS: + digest = getattr(digests, field_name) + memoized_digest = getattr(memoized_digests, field_name, None) + if memoized_digest is None or memoized_digest == digest: + continue + if field_name == "model_id": + logger.warning( + "*** POSSIBLE NON-DETERMINISM! *** " + f"Agent '{agent_name}': configured model " + f"'{digest}' differs from the model " + f"'{memoized_digest}' used when this agent " + "run was first executed; previously " + "memoized model responses came from a " + "different model and may not reflect what " + "the current model would produce." + ) + else: + logger.warning( + "*** POSSIBLE NON-DETERMINISM! *** " + f"Agent '{agent_name}': {label} differs " + "from the snapshot taken when this agent " + "run was first executed; previously " + "memoized model responses may no longer " + "reflect the current configuration." + ) + + yield + finally: + _workflow_context.set(None) diff --git a/reboot/agents/pydantic_ai/_toolset.py b/reboot/agents/pydantic_ai/_toolset.py new file mode 100644 index 00000000..dfcdd09f --- /dev/null +++ b/reboot/agents/pydantic_ai/_toolset.py @@ -0,0 +1,396 @@ +from __future__ import annotations + +import functools +import inspect +import pydantic_ai.tools +import typing +from ._run import _require_workflow_context +from ._typing import AgentDepsT, ToolParams +from collections.abc import Callable, Sequence +from pydantic_ai._run_context import RunContext +from pydantic_ai.exceptions import UserError +from pydantic_ai.messages import InstructionPart +from pydantic_ai.tools import ToolDefinition +from pydantic_ai.toolsets.abstract import AbstractToolset, ToolsetTool +from pydantic_ai.toolsets.wrapper import WrapperToolset +from reboot.aio.contexts import WorkflowContext +from reboot.aio.workflows import at_least_once +from typing import Any, Concatenate, Optional, TypeAlias, cast, get_origin + +# NOTE: this file is using the `T | None` style for optional types +# instead of `Optional[T]` because that is how pydantic_ai does it and +# a lot of the code here is copied from their repository and we'd like +# to keep it consistent as we update it and keep it in sync with the +# upstream pydantic_ai library. + +# Mirrors `pydantic_ai.tools.ToolFuncContext` but with a leading +# `WorkflowContext` parameter, matching the signature accepted by +# `Agent.tool`. +ToolFuncContext: TypeAlias = Callable[ + Concatenate[WorkflowContext, RunContext[AgentDepsT], ToolParams], + Any, +] + +# `Agent.tool_plain` accepts the same shape as +# `pydantic_ai.Agent.tool_plain`, so we just point at pydantic_ai's +# alias rather than maintaining our own copy. +ToolFuncPlain = pydantic_ai.tools.ToolFuncPlain + + +class _WrapperToolset(WrapperToolset[AgentDepsT]): + """An `AbstractToolset` wrapper that intercepts every tool invocation + and runs it inside a Reboot `at_least_once(...)` and + `context.idempotency_seeds(...)` so that completed tools aren't + re-run on workflow replay (but ARE re-run during effect + validation, to expose non-determinism / undeclared side effects). + + All three pydantic_ai tool registration paths (`@agent.tool`, + `tools=`, `toolsets=`) converge at + `AbstractToolset.call_tool`, so wrapping every leaf toolset + intercepts every tool call -- including parallel ones + dispatched via `asyncio.create_task`. + + Per-call disambiguation uses + `(tool_name, tool_call_id, run_step)` from the active + `RunContext`. `tool_call_id` is effectively deterministic on + replay because the upstream `Model.request` already memoizes + the entire `ModelResponse`, so the cached response carries the + same `tool_call_id` even when pydantic_ai's + `guard_tool_call_id` had to fill one in. + """ + + async def call_tool( + self, + name: str, + tool_args: dict[str, Any], + run: RunContext[AgentDepsT], + tool: ToolsetTool[AgentDepsT], + ) -> Any: + context = _require_workflow_context() + with context.idempotency_seeds( + { + "tool_name": name, + "tool_call_id": run.tool_call_id, + "run_step": run.run_step, + } + ): + + async def call() -> Any: + return await self.wrapped.call_tool(name, tool_args, run, tool) + + # `at_least_once` infers `type` from `call`'s return + # annotation. `Any` becomes `object` at the runtime + # check, which is what we want -- a tool can return + # anything pickle-able. + return await at_least_once( + f"Tool call for step #{run.run_step}", + context, + call, + ) + + def visit_and_replace( + self, + visitor: Callable[[AbstractToolset[AgentDepsT]], + AbstractToolset[AgentDepsT]], + ) -> AbstractToolset[AgentDepsT]: + # We never expose our internal `_WrapperToolset` and we don't + # expect it to be wrapped further, so for now we just treat + # any subsequent calls to visit and replace as a no-op. + return self + + +# Lazily-imported MCP toolset classes. Both `pydantic_ai.mcp` and +# `pydantic_ai.toolsets.fastmcp` are optional pydantic_ai extras, so +# we can't depend on them being importable. We pin the import to +# module load time and let the dispatch below skip detection when +# either is absent. +try: + from pydantic_ai.mcp import MCPServer +except ImportError: + MCPServer = None # type: ignore[assignment, misc] +try: + from pydantic_ai.toolsets.fastmcp import FastMCPToolset +except ImportError: + FastMCPToolset = None # type: ignore[assignment, misc] + + +class _MCPWrapperToolset(_WrapperToolset[AgentDepsT]): + """Variant of `_WrapperToolset` for MCP-typed toolsets + (`pydantic_ai.mcp.MCPServer`, + `pydantic_ai.toolsets.fastmcp.FastMCPToolset`). On top of the + `call_tool` wrapping that `_WrapperToolset` already does, also + routes `get_tools` and `get_instructions` through `at_least_once`. + """ + + def __init__(self, wrapped: AbstractToolset[AgentDepsT]) -> None: + super().__init__(wrapped) + # Wrapper-level cache for `get_tools` results; populated + # on first fetch when `wrapped.cache_tools` is true. + # + # We do our own caching because `MCPServer` only caches + # _within_ an `async with ...` block and we don't use those, + # see comments for `__aenter__` and `__aexit__`. + # + # Technically we don't need to do this for correctness but the + # other durable execution implementations do so we followed suit. + self._cached_tool_defs: Optional[dict[str, ToolDefinition]] = None + + @property + def id(self) -> str | None: + # `WrapperToolset.id` returns `None` by default; surface + # the wrapped MCP toolset's `id` so the `at_least_once` + # aliases below disambiguate per MCP server. + return self.wrapped.id + + # No-op (vs. `WrapperToolset.__aenter__` which forwards to + # `wrapped`) so the run-level `async with toolset:` doesn't + # open a connection that has to span replays/retries. + async def __aenter__(self) -> "_MCPWrapperToolset[AgentDepsT]": + return self + + # Paired with `__aenter__` above: keep the wrapped + # connection lifecycle out of the run-level scope. + async def __aexit__(self, *args: Any) -> Optional[bool]: + return None + + def _tool_for_tool_def( + self, + tool_def: ToolDefinition, + ) -> ToolsetTool[AgentDepsT]: + # `tool_for_tool_def` is declared on `MCPServer` (`mcp.py`) + # and `FastMCPToolset` (`toolsets/fastmcp.py`) but NOT on the + # `AbstractToolset` base type which is the type of + # `self.wrapped`, so we need a `cast` to call it. Cast the + # return too: `MCPServer.tool_for_tool_def` is typed + # `ToolsetTool[Any]` and `FastMCPToolset`'s is + # `ToolsetTool[AgentDepsT]`, so the union (over both + # branches) doesn't unify with our wrapper's + # `ToolsetTool[AgentDepsT]` return type. + wrapped = cast("MCPServer | FastMCPToolset", self.wrapped) + return cast( + "ToolsetTool[AgentDepsT]", + wrapped.tool_for_tool_def(tool_def), + ) + + async def get_tools( + self, + run: RunContext[AgentDepsT], + ) -> dict[str, ToolsetTool[AgentDepsT]]: + wrapped = self.wrapped + # When the user wants tool-list caching, short-circuit before + # `at_least_once`: pydantic_ai documents `cache_tools=True` + # (the default) in durable execution as "fetched once and + # cached for the duration of the workflow run, not invalidated + # by `tools/list_changed` notifications" (see + # `pydantic_ai.mcp.MCPServer`'s `cache_tools` docstring). The + # wrapped MCP server's own `_cached_tools` doesn't survive + # across our memoized fetches because it is only valid within + # an `async with ...` block and we short-circuit `__aenter__` + # / `__aexit__`. + # + # We use `getattr(wrapped, "cache_tools", False)` rather than + # reading `wrapped.cache_tools` directly because only + # `MCPServer` defines `cache_tools`, `FastMCPToolset` doesn't. + cache_tools = getattr(wrapped, "cache_tools", False) + + if cache_tools and self._cached_tool_defs is not None: + return { + name: self._tool_for_tool_def(tool_def) + for name, tool_def in self._cached_tool_defs.items() + } + + context = _require_workflow_context() + + async def fetch() -> dict[str, ToolDefinition]: + tools = await wrapped.get_tools(run) + # Strip down to bare `ToolDefinition`s before + # `at_least_once` memoizes the result because a + # `ToolsetTool` includes a `SchemaValidator` (the same one + # for every MCP tool) which is not pickleable. The + # `ToolsetTool`s are reconstructed below via + # `tool_for_tool_def`. + return {name: tool.tool_def for name, tool in tools.items()} + + tool_defs = await at_least_once( + f"MCP get_tools for {self.id} step #{run.run_step}", + context, + fetch, + ) + + if cache_tools: + self._cached_tool_defs = tool_defs + + # Reconstruct the `ToolsetTool`s outside the `at_least_once` + # because `fetch()` stripped them to bare `ToolDefinition`s so + # that we could pickle. The wrapped toolset's + # `tool_for_tool_def` re-attaches the `SchemaValidator` that + # callers downstream of `get_tools` expect. + return { + name: self._tool_for_tool_def(tool_def) + for name, tool_def in tool_defs.items() + } + + async def get_instructions( + self, + run: RunContext[AgentDepsT], + ) -> str | InstructionPart | Sequence[str | InstructionPart] | None: + wrapped = self.wrapped + + # `MCPServer` and `FastMCPToolset` both short-circuit to + # `None` when `include_instructions=False`, so skip the + # `at_least_once` in that case. + # + # Other durable execution implementations do this so we are as + # well. + if not getattr(wrapped, "include_instructions", False): + return None + + context = _require_workflow_context() + + # pydantic_ai considers instructions "dynamic since they may + # change between connections" and there's no + # `cache_instructions` toggle (so no wrapper-level cache like + # for `get_tools`) so we need to fetch each time! + async def fetch( + ) -> str | InstructionPart | Sequence[str | InstructionPart] | None: + # NOTE: most methods on `MCPServer` and `FastMCPToolset` + # do not need to be entered but `get_instructions` is an + # exception hence the `async with wrapped` here! + async with wrapped: + return await wrapped.get_instructions(run) + + return await at_least_once( + f"MCP get_instructions for {self.id} step #{run.run_step}", + context, + fetch, + ) + + +def _make_toolset_wrapper( +) -> Callable[[AbstractToolset[AgentDepsT]], AbstractToolset[AgentDepsT]]: + """Build a `visit_and_replace`-compatible callback that wraps each + toolset in the right Reboot wrapper: `_MCPWrapperToolset` for MCP + toolsets (so `get_tools` / `get_instructions` / `call_tool` all + flow through `at_least_once`) and the plain `_WrapperToolset` for + everything else (where only `call_tool` needs memoization since + `get_tools` / `get_instructions` are pure local lookups). + + Returns a callable that remembers across each invocation whether + there has been more than one MCP toolset whose `id` is `None` and + raises `UserError` if so. The `at_least_once` aliases for + `get_tools` / `get_instructions` embed `self.id` to disambiguate + per MCP toolset, so two toolsets without an `id` in the same agent + run would silently clobber each other on replay. A single server + without an `id` is fine. + """ + seen_mcp_toolset_without_id: bool = False + + def wrap( + toolset: AbstractToolset[AgentDepsT], + ) -> AbstractToolset[AgentDepsT]: + nonlocal seen_mcp_toolset_without_id + is_mcp = ( + (MCPServer is not None and isinstance(toolset, MCPServer)) or ( + FastMCPToolset is not None and + isinstance(toolset, FastMCPToolset) + ) + ) + if is_mcp: + if toolset.id is None: + if seen_mcp_toolset_without_id: + raise UserError( + "Found more than one MCP toolset without an `id`. " + "Reboot uses each MCP toolset's `id` to " + "disambiguate `get_tools` / " + "`get_instructions` / `call_tool` results " + "across workflow replays. Pass `id=...` to all " + "but at most one of your MCP toolsets (or " + "for `MCPServer`, `tool_prefix=...`, which " + "doubles as the id when `id=` isn't set)." + ) + seen_mcp_toolset_without_id = True + return _MCPWrapperToolset(toolset) + return _WrapperToolset(toolset) + + return wrap + + +def _wrap_tool_function(function: Any) -> Any: + """Build a pydantic_ai-compatible wrapper around a Reboot tool + function whose first two parameters are a `WorkflowContext` and a + `RunContext`. + + The wrapper exposes a signature with the leading `WorkflowContext` + parameter dropped, so pydantic_ai's `RunContext`-detection and + JSON-schema generation see the user's intended pydantic_ai surface + (`(run: RunContext[Deps], …)`). At call time the wrapper retrieves + the active `WorkflowContext` from `_require_workflow_context()` + and forwards `(workflow_context, *args, **kwargs)` to the original + function. + + The Reboot type system (`ToolFuncContext`) already enforces the + full `(WorkflowContext, RunContext[Deps], ...)` type signature + statically, but here we ensure at runtime that this is the case: + parameter count first, then annotations on the first two + parameters whenever the user supplied them. Unannotated + parameters are accepted on faith. + """ + signature = inspect.signature(function) + parameters = list(signature.parameters.values()) + if len(parameters) < 2: + raise TypeError( + f"`{function.__name__}` must accept " + "`context: WorkflowContext` and `run: RunContext[Deps]` " + "as its first two parameters when registered via " + "`Agent.tool`; for tools that don't need a " + "`RunContext`, use `Agent.tool_plain` instead." + ) + + # Resolve annotations once via `typing.get_type_hints` so + # string forward references (`from __future__ import + # annotations` style) are evaluated. If resolution fails for + # any reason, fall back to the raw annotation values. + try: + type_hints = typing.get_type_hints(function) + except Exception: + type_hints = {} + + first_annotation = type_hints.get( + parameters[0].name, parameters[0].annotation + ) + if first_annotation is not inspect.Parameter.empty and not ( + first_annotation is WorkflowContext or ( + isinstance(first_annotation, type) and + issubclass(first_annotation, WorkflowContext) + ) + ): + raise TypeError( + f"`{function.__name__}`'s first parameter is annotated " + f"as '{first_annotation}', but `Agent.tool` requires " + "it to be `context: WorkflowContext`." + ) + + second_annotation = type_hints.get( + parameters[1].name, parameters[1].annotation + ) + if second_annotation is not inspect.Parameter.empty and not ( + second_annotation is RunContext or + get_origin(second_annotation) is RunContext + ): + raise TypeError( + f"`{function.__name__}`'s second parameter is " + f"annotated as '{second_annotation}', but `Agent.tool` " + "requires it to be `run: RunContext[Deps]`. For tools " + "that don't need a `RunContext`, use " + "`Agent.tool_plain` instead." + ) + + @functools.wraps(function) + async def wrapper(*args: Any, **kwargs: Any) -> Any: + return await function(_require_workflow_context(), *args, **kwargs) + + wrapper.__signature__ = signature.replace( # type: ignore[attr-defined] + parameters=parameters[1:] + ) + + return wrapper diff --git a/reboot/agents/pydantic_ai/_typing.py b/reboot/agents/pydantic_ai/_typing.py new file mode 100644 index 00000000..298534b9 --- /dev/null +++ b/reboot/agents/pydantic_ai/_typing.py @@ -0,0 +1,26 @@ +from typing_extensions import ParamSpec, TypeVar + +# This file exists because our `mypy.ini` declares +# `[mypy-pydantic_ai.*] ignore_missing_imports = True` and thus +# pydantic_ai is treated as `Any` by mypy, which means we can't +# usefully import pydantic_ai's `TypeVar` / `ParamSpec` objects +# (`AgentDepsT`, `OutputDataT`, `RunOutputDataT`, `ToolParams`) and +# have them work in mypy's eyes. Importing them gets `Any`, which +# fails the constraints for `Generic[...]` parents, generic type +# aliases, and the last position of `Concatenate[...]` -- producing +# a cascade of `[type-arg]` / `[valid-type]` errors. +# +# The fix (for now) is to define our own copies here. The definitions +# are exact copies of pydantic_ai's. +# +# These are runtime-distinct from pydantic_ai's identically-named +# `TypeVar` objects, but pydantic_ai's generic plumbing doesn't +# compare `TypeVar`'s by identity across modules so runtime +# behaviour is unaffected. +# +# `typing_extensions` is used because PEP 696's `default=` only landed +# in stdlib `typing` in Python 3.13 and we target 3.10+. +AgentDepsT = TypeVar("AgentDepsT", default=None, contravariant=True) +OutputDataT = TypeVar("OutputDataT", default=str, covariant=True) +RunOutputDataT = TypeVar("RunOutputDataT") +ToolParams = ParamSpec("ToolParams", default=...) diff --git a/reboot/aio/BUILD.bazel b/reboot/aio/BUILD.bazel index d3faa162..3c0ee8b3 100644 --- a/reboot/aio/BUILD.bazel +++ b/reboot/aio/BUILD.bazel @@ -348,16 +348,6 @@ py_library( ], ) -py_library( - name = "secrets_py", - srcs = ["secrets.py"], - srcs_version = "PY3", - visibility = ["//visibility:public"], - deps = [ - "//reboot:settings_py", - ], -) - py_library( name = "servers_py", srcs = ["servers.py"], diff --git a/reboot/aio/auth/BUILD.bazel b/reboot/aio/auth/BUILD.bazel index 583a8877..a5f373fe 100644 --- a/reboot/aio/auth/BUILD.bazel +++ b/reboot/aio/auth/BUILD.bazel @@ -21,7 +21,6 @@ py_library( "//reboot:run_environments_py", "//reboot/aio:aborted_py", "//reboot/aio:headers_py", - "//reboot/aio:secrets_py", "//reboot/controller:settings_py", "@com_github_reboot_dev_reboot//rbt/v1alpha1/admin:auth_py_reboot", ], diff --git a/reboot/aio/auth/admin_auth.py b/reboot/aio/auth/admin_auth.py index ff1a6bb3..5fbcbb69 100644 --- a/reboot/aio/auth/admin_auth.py +++ b/reboot/aio/auth/admin_auth.py @@ -4,10 +4,9 @@ import rbt.v1alpha1.admin.auth_pb2_grpc as admin_auth_pb2_grpc from log.log import get_logger from reboot.aio.headers import AUTHORIZATION_HEADER -from reboot.aio.secrets import SecretNotFoundException, Secrets from reboot.controller.settings import ENVVAR_REBOOT_ADMIN_AUTH_URL from reboot.run_environments import running_rbt_dev -from reboot.settings import ADMIN_SECRET_NAME +from reboot.settings import ENVVAR_SECRET_REBOOT_ADMIN_TOKEN from typing import Optional logger = get_logger(__name__) @@ -29,16 +28,10 @@ def auth_metadata_from_metadata( class AdminAuthMixin: - """Mixin that is used to provide a helper for checking that a request - contains the necessary admin credentials. - - We use a mixin over a free standing function to avoid a global `Secrets` object. + """Mixin that is used to provide a helper for checking that a + request contains the necessary admin credentials. """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.__secrets = Secrets() - async def ensure_admin_auth_or_fail( self, grpc_context: grpc.aio.ServicerContext, @@ -76,19 +69,18 @@ async def ensure_admin_auth_or_fail( ) raise - admin_secret: str - try: - admin_secret = (await - self.__secrets.get(ADMIN_SECRET_NAME)).decode() - + admin_secret = os.environ.get(ENVVAR_SECRET_REBOOT_ADMIN_TOKEN) + if admin_secret is not None: if bearer_token == admin_secret: - # The provided bearer token matches the configured admin secret. - # This is sufficient to authorize the request. + # The provided bearer token matches the + # configured admin secret. This is sufficient to + # authorize the request. return logger.info( - "An admin access request was made, but the provided " - "bearer token did not match the configured admin secret" + "An admin access request was made, but the " + "provided bearer token did not match the " + "configured admin secret" ) await grpc_context.abort( @@ -98,13 +90,10 @@ async def ensure_admin_auth_or_fail( raise AssertionError # For `mypy`. - except SecretNotFoundException: - logger.debug( - "Admin secret '%s' not found. " - "Going to check if an external admin auth application " - "is configured.", - ADMIN_SECRET_NAME, - ) + logger.debug( + f"{ENVVAR_SECRET_REBOOT_ADMIN_TOKEN} env var not found. Going to " + "check if an external admin auth application is configured.", + ) # If an external application is configured as being responsible for # admin auth, delegate to it. diff --git a/reboot/aio/contexts.py b/reboot/aio/contexts.py index bdf92bd8..ef455320 100644 --- a/reboot/aio/contexts.py +++ b/reboot/aio/contexts.py @@ -27,6 +27,9 @@ How, Idempotency, IdempotencyManager, + _merge_idempotency_seeds, + make_derived_idempotency_key, + make_idempotency_alias, ) from reboot.aio.internals.channel_manager import ( LegacyGrpcChannel, @@ -68,6 +71,32 @@ ResponseT = TypeVar('ResponseT', bound=Message) +class IterationCompleteCallable(Protocol): + """Callable signature accepted by + `WorkflowContext.on_iteration_complete`. May be sync (returning + `None`) or async (returning an `Awaitable[None]`). The runtime + awaits async returns to completion before continuing. + """ + + def __call__(self, *, iteration: int) -> None | Awaitable[None]: + ... + + +class MethodCompleteCallable(Protocol): + """Callable signature accepted by + `WorkflowContext.on_method_complete`. May be sync (returning + `None`) or async (returning an `Awaitable[None]`). The runtime + awaits async returns to completion before continuing. + + `retrying=True` means the workflow method body raised and the + framework will retry it; `retrying=False` means the method body + returned normally or raised a declared error. + """ + + def __call__(self, *, retrying: bool) -> None | Awaitable[None]: + ... + + class Participants: # Participants that we should commit. _should_commit: defaultdict[StateTypeName, set[StateRef]] @@ -1219,6 +1248,11 @@ class WorkflowContext(Context): _reactively_state_manager: StateManager _reactively_state_type: type + # Callables for when the current `context.loop(...)` iteration + # completes and when the method body returns or raises. + _on_iteration_complete: list[IterationCompleteCallable] + _on_method_complete: list[MethodCompleteCallable] + def __init__( self, *, @@ -1245,6 +1279,69 @@ def __init__( self._reactively_state_manager = reactively_state_manager self._reactively_state_type = reactively_state_type + self._on_iteration_complete = [] + self._on_method_complete = [] + + def on_iteration_complete( + self, + callable: IterationCompleteCallable, + ) -> None: + """Register `callable` to be called every time a + `context.loop(...)` iteration completes during this workflow + method invocation, with the just-finished iteration number + passed as `iteration=`. Callables stay registered across + iterations and are cleared only when the workflow method + itself completes (see `on_method_complete`) -- callers + register once per method invocation, not once per iteration. + Callables may be sync or async; if async, the runtime awaits + them to completion before continuing. + + Raising from this callable propagates out of the iteration + body and the entire workflow method gets retried (including + the iteration whose completion the callable was called). + """ + self._on_iteration_complete.append(callable) + + def on_method_complete( + self, + callable: MethodCompleteCallable, + ) -> None: + """Register `callable` to be called when this workflow method body + finishes -- whether by returning a result or raising a + declared error (`retrying=False`) or by raising an unexpected + error that will cause the method to be retried + (`retrying=True`). Calls exactly once per method invocation; + afterwards both `on_iteration_complete` and + `on_method_complete` lists are cleared so a re-run on the same + context starts with a fresh registration. Callables may be + sync or async; if async, the runtime awaits them to completion + before continuing. + + Raising from this callable propagates out of the workflow + method body and the entire workflow method gets retried. + """ + self._on_method_complete.append(callable) + + @contextmanager + def idempotency_seeds( + self, + entries: dict[str, Any], + ) -> Iterator[None]: + """Push key=value `entries` that scope every + `Idempotency`-keyed call (`at_least_once`, `at_most_once`, + `until`, idempotent method calls, inline writer idempotency) + made inside the `with` block. Nested blocks merge with + inner-overrides-outer semantics; the outer state is restored + on exit. + + Exposed on `WorkflowContext` and not `Context` because + composing extra seeds only makes sense when the context + already has an idempotency seed -- a `WorkflowContext` always + does (seeded from the task's UUID). + """ + with _merge_idempotency_seeds(entries): + yield + def within_until(self) -> bool: return _within_until.get() @@ -1274,12 +1371,20 @@ def make_idempotency_key( and produce a single key for the entire workflow regardless of iteration. """ - if self.within_loop() and not per_workflow: - alias += f" (iteration #{self.task.iteration})" + iteration = ( + self.task.iteration + if self.within_loop() and not per_workflow else None + ) + + alias_iteration = make_idempotency_alias(alias, iteration) + + assert alias_iteration is not None + # NOTE: `self.workflow_id` is the same as the task ID which is + # also what we use as the seed in IdempotencyManager. assert self.workflow_id is not None - return uuid.uuid5(self.workflow_id, alias) + return make_derived_idempotency_key(self.workflow_id, alias_iteration) @property def workflow_iteration(self) -> Optional[int]: diff --git a/reboot/aio/idempotency.py b/reboot/aio/idempotency.py index d6c33548..7b213975 100644 --- a/reboot/aio/idempotency.py +++ b/reboot/aio/idempotency.py @@ -1,5 +1,7 @@ +import pickle import uuid from contextlib import contextmanager +from contextvars import ContextVar from dataclasses import dataclass from datetime import datetime, timedelta from google.protobuf.message import Message @@ -15,7 +17,7 @@ validate_ascii, ) from reboot.settings import DOCS_BASE_URL, MAX_IDEMPOTENCY_KEY_LENGTH -from typing import Iterator, Literal, Optional, TypeAlias +from typing import Any, Iterator, Literal, Optional, TypeAlias from uuid7 import create as uuid7 # type: ignore[import] # NOTE: we're not using an enum because the values that can be @@ -29,6 +31,108 @@ Literal["ALWAYS"] | Literal["PER_WORKFLOW"] | Literal["PER_ITERATION"] ) +# Stable UUID for deterministically combining the stringified version +# of `idempotency_seeds(...)`. +_REBOOT_IDEMPOTENCY_SEEDS_NAMESPACE: uuid.UUID = uuid.uuid5( + uuid.NAMESPACE_DNS, "idempotency_seeds.reboot.dev" +) + +# Holds the currently-merged idempotency seeds for any active +# `idempotency_seeds(...)` block. Needed so nested blocks can compose +# with inner-overrides-outer semantics. +_idempotency_seeds: ContextVar[Optional[dict[str, Any]]] = ContextVar( + "Idempotency seeds (if any) to use for generating idempotency keys", + default=None, +) + +# Holds the precomputed UUID derived from the idempotency +# seeds. Consumers (e.g., `memoize`, `_idempotency_key`) read this +# directly so they don't have to recompute the UUID on every memoized +# / idempotent call. +_idempotency_seeds_uuid: ContextVar[Optional[uuid.UUID]] = ContextVar( + "UUID representing the idempotency seeds (if any)", + default=None, +) + + +def make_idempotency_alias( + base: Optional[str], + iteration: Optional[int], +) -> Optional[str]: + """ + Make an idempotency alias string from an optional base alias and an + optional control-loop `iteration`. + """ + if base is None and iteration is None: + return None + alias = base if base is not None else "-" + if iteration is not None: + return f"{alias} (iteration #{iteration})" + return alias + + +@contextmanager +def _merge_idempotency_seeds(entries: dict[str, Any]) -> Iterator[None]: + """Internal context manager used by + `WorkflowContext.idempotency_seeds(...)`. Not part of the + public API -- callers should always reach it through a + seed-bearing context (today only `WorkflowContext`). + + Push key=value entries that scope every `Idempotency`-keyed + call inside the `with` block. + + All of the values in `entries` must be pickleable and their + pickled represenation should not _ever_ change, otherwise we may + generate different idempotency keys from these seeds. + + Every consumer of `Idempotency` -- `at_least_once`, + `at_most_once`, `until`, idempotent method calls, inline writer + idempotency -- routes through the same + `make_derived_idempotency_key` helper, so a single seeds block + scopes all of them uniformly. + + Nested `with` blocks merge with inner-overrides-outer semantics: + inner entries shadow outer entries on matching keys, and the outer + state is restored on exit. + + NOTE: entering a seeds block perturbs every idempotency key + derived inside it. Any in-flight memoized iteration or + outstanding idempotency key whose key was derived without the + seeds will *not* match the new seeded key; those calls will + re-execute. This is the expected behaviour -- if you add + seeds, you've defined a new scope. + """ + idempotency_seeds = _idempotency_seeds.get() or {} + merged = {**idempotency_seeds, **entries} + idempotency_seeds_uuid = uuid.uuid5( + _REBOOT_IDEMPOTENCY_SEEDS_NAMESPACE, + # We're pinning to `protocol=4` so the derived UUID stays + # stable across Python upgrades that might change + # `pickle.DEFAULT_PROTOCOL`. + pickle.dumps(sorted(merged.items()), protocol=4).hex(), + ) + previous_idempotency_seeds = _idempotency_seeds.set(merged) + previous_idempotency_seeds_uuid = _idempotency_seeds_uuid.set( + idempotency_seeds_uuid + ) + try: + yield + finally: + _idempotency_seeds_uuid.reset(previous_idempotency_seeds_uuid) + _idempotency_seeds.reset(previous_idempotency_seeds) + + +def make_derived_idempotency_key(seed: uuid.UUID, alias: str) -> uuid.UUID: + """ + Derive a `uuid5` from `(seed, alias)`, folding the + currently-active (if any) `idempotency_seeds(...)` UUID into the + seed first when one is in scope. + """ + idempotency_seeds_uuid = _idempotency_seeds_uuid.get() + if idempotency_seeds_uuid is not None: + seed = uuid.uuid5(seed, str(idempotency_seeds_uuid)) + return uuid.uuid5(seed, alias) + def make_expiring_idempotency_key( when: timedelta = timedelta(days=7), @@ -115,17 +219,11 @@ def __init__( @property def alias(self) -> Optional[str]: - """Returns the alias, scoped by iteration when applicable. - - When `how=PER_ITERATION`, the iteration number is appended to - the alias so that all consumers (including `memoize()` and - `_get_or_create_idempotency_key()`) see an iteration-scoped - alias without needing to append it themselves. + """Returns the alias, scoped by iteration and any active + `alias_metadata(...)` block. See `make_idempotency_alias` for + the composition rules. """ - if self._iteration is not None: - alias = self._alias if self._alias is not None else "-" - return f"{alias} (iteration #{self._iteration})" - return self._alias + return make_idempotency_alias(self._alias, self._iteration) @property def key(self) -> Optional[uuid.UUID]: @@ -192,7 +290,7 @@ class RPC: class Checkpoint: """Captures all `IdempotencyManager` instance variables for checkpoint/restore functionality.""" - aliases: dict[tuple[StateRef, str], uuid.UUID] + aliases: dict[tuple[StateRef, str, Optional[uuid.UUID]], uuid.UUID] rpcs: dict[uuid.UUID, RPC] mutations_without_idempotency: bool uncertain_mutation: bool @@ -204,8 +302,16 @@ class Checkpoint: class IdempotencyManager: - # Map from (state_ref, alias) to a generated UUID for an idempotency key. - _aliases: dict[tuple[StateRef, str], uuid.UUID] + # Map from (state_ref, alias, idempotency_seeds_uuid) to a generated + # UUID for an idempotency key. The third tuple element is the active + # `_idempotency_seeds_uuid` (or `None` if no `idempotency_seeds(...)` + # block is active) so two calls under the same `(state_ref, alias)` + # but in different seed scopes get distinct slots -- otherwise + # the first call would "claim" the slot for the lifetime of the + # manager and subsequent calls in different scopes would receive the + # first call's UUID, defeating the "new scope" semantics that + # `idempotency_seeds(...)` promises. + _aliases: dict[tuple[StateRef, str, Optional[uuid.UUID]], uuid.UUID] # Map from idempotency key to its RPC. _rpcs: dict[uuid.UUID, RPC] @@ -532,7 +638,14 @@ def _get_or_create_idempotency_key( if idempotency.alias is not None: alias += f": {idempotency.alias}" - key = (state_ref, alias) + # Include the idempotency seeds (if any) in the key so calls + # in different seed scopes don't collide. The seeds are also + # folded into the cached UUID's value below (via + # `make_derived_idempotency_key`), but if we don't also use + # the seeds in the key then we may get a false collision and + # short-circuit thinking we already have an idempotency key + # when we don't. + key = (state_ref, alias, _idempotency_seeds_uuid.get()) if key not in self._aliases: if self._seed is None: @@ -555,12 +668,20 @@ def _get_or_create_idempotency_key( # 'stubs.py'), but then we won't give people the false # believe that because they called `.idempotently()` # it'll be safe "forever". + # + # Invariant: idempotency seeds can only be added via + # `WorkflowContext.idempotency_seeds(...)`, and a + # `WorkflowContext` always has a `_seed`, so if we're + # in the no-seed branch, we shouldn't have any extra + # idempotency seeds. We're asserting this so that if + # `_merge_idempotency_seeds()` gets called on its own + # in a place where it should not we catch the bug. + assert _idempotency_seeds.get() is None self._aliases[key] = make_expiring_idempotency_key() else: - # A version 5 UUID is a deterministic hash from a - # "seed" UUID and some data (bytes or string, in our - # case the string `alias`). - self._aliases[key] = uuid.uuid5(self._seed, alias) + self._aliases[key] = make_derived_idempotency_key( + self._seed, alias + ) return self._aliases[key] @@ -580,12 +701,23 @@ def generate_idempotent_state_id( ) if idempotency.key is not None: - # Generate a UUID based on the specified key. - return str(uuid.uuid5(self._seed, str(idempotency.key))) + # Need to use `make_derived_idempotency_key` so the active + # idempotency seeds (if any) are folded into the seed. + # That keeps the generated state ID scoped consistently + # with any per-RPC idempotency keys produced by + # `_get_or_create_idempotency_key`. + return str( + make_derived_idempotency_key(self._seed, str(idempotency.key)) + ) alias = ( idempotency.alias if idempotency.alias is not None else f'{self._rpc_name(state_type_name, service_name, method, True)}' ) - return str(uuid.uuid5(self._seed, alias)) + # Need to use `make_derived_idempotency_key` so the active + # idempotency seeds (if any) are folded into the seed. That + # keeps the generated state ID scoped consistently with any + # per-RPC idempotency keys produced by + # `_get_or_create_idempotency_key`. + return str(make_derived_idempotency_key(self._seed, alias)) diff --git a/reboot/aio/memoize.py b/reboot/aio/memoize.py index f9ffbfc3..5b9a5afc 100644 --- a/reboot/aio/memoize.py +++ b/reboot/aio/memoize.py @@ -9,11 +9,15 @@ WriterContext, _log_message_for_effect_validation, ) +from reboot.aio.idempotency import make_derived_idempotency_key from reboot.aio.types import assert_type from reboot.aio.workflows import ( ALWAYS, PER_ITERATION, AtMostOnceFailedBeforeCompleting, + TypeT, + _format_type, + _isinstance_type, ) from reboot.memoize.v1.memoize_rbt import ( FailRequest, @@ -53,7 +57,8 @@ async def memoize( context: WorkflowContext, callable: Callable[[], Awaitable[T]], *, - type_t: type[T], + type_t: TypeT, + type_t_inferred: bool = False, at_most_once: bool, until: bool = False, retryable_exceptions: Optional[list[type[Exception]]] = None, @@ -61,6 +66,13 @@ async def memoize( """Memoizes the result of running `callable`, only attempting to do so once if `at_most_once=True`. + `type_t_inferred=True` indicates that `type_t` was inferred from + `callable`'s return annotation rather than passed explicitly by + the caller. This only affects the wording of the `TypeError` + raised when the runtime value disagrees with `type_t`, so the + caller knows whether to fix the annotation or pass `type=` + explicitly. + NOTE: this is the Python wrapper for `reboot.memoize.v1` and as such uses `pickle` to serialize the result of calling `callable` which therefore must be pickle-able. @@ -93,7 +105,7 @@ async def memoize( # # TODO(benh): colocate with `context.state_ref` for performance. memoize = Memoize.ref( - str(uuid.uuid5(context.task_id, idempotency.alias)), + str(make_derived_idempotency_key(context.task_id, idempotency.alias)), ) await memoize.idempotently( @@ -114,11 +126,21 @@ async def memoize( ) elif status.stored: t = pickle.loads(status.data) - if type(t) is not type_t: + if not isinstance(t, _isinstance_type(type_t)): + if type_t_inferred: + raise TypeError( + f"Stored result of type '{type(t).__name__}' from " + "'callable' is not compatible with the expected type " + f"'{_format_type(type_t)}' inferred from the " + "callable's return annotation; has the annotation " + "changed since the result was stored? Update the " + "annotation or pass `type=` explicitly." + ) raise TypeError( - f"Stored result of type '{type(t).__name__}' from 'callable' " - f"is not of expected type '{type_t.__name__}'; have you changed " - "the 'type' that you expect after having stored a result?" + f"Stored result of type '{type(t).__name__}' from " + "'callable' is not compatible with the expected type " + f"'{_format_type(type_t)}'; have you changed the 'type' that " + "you expect after having stored a result?" ) return t @@ -219,16 +241,38 @@ async def callable_validating_effects(): # validation and were confused. See # https://github.com/reboot-dev/mono/issues/4616 for more # details. - if type(t) is not type_t: + if not isinstance(t, _isinstance_type(type_t)): # NOTE: this error will only apply to Python developers # and hence we use Python names, e.g., `at_least_once`, # because we know that the Node.js code will always pass # the correct `type_t` (or else we have an internal bug). + primitive = ( + 'at_most_once' if at_most_once else + ('until' if until else 'at_least_once') + ) + if type_t_inferred: + if type_t is type(None): + raise TypeError( + f"Result of type '{type(t).__name__}' from " + f"callable passed to '{primitive}' is not `None` " + "but no `type=` argument was passed and " + "no return annotation was found on the " + "callable; either annotate the callable's " + "return type or pass `type=` explicitly." + ) + raise TypeError( + f"Result of type '{type(t).__name__}' from " + f"callable passed to '{primitive}' is not " + "compatible with the expected type " + f"'{_format_type(type_t)}' inferred from the " + "callable's return annotation; fix the " + "annotation or pass `type=` explicitly." + ) raise TypeError( - f"Result of type '{type(t).__name__}' from callable passed to " - f"'{'at_most_once' if at_most_once else ('until' if until else 'at_least_once')}' " - f"is not of expected type '{type_t.__name__}'; " - "did you specify an incorrect 'type' or _forget_ to specify " + f"Result of type '{type(t).__name__}' from callable " + f"passed to '{primitive}' is not of expected type " + f"'{_format_type(type_t)}'; did you specify an incorrect " + "'type' or _forget_ to specify " "the keyword argument 'type' all together?" ) diff --git a/reboot/aio/secrets.py b/reboot/aio/secrets.py deleted file mode 100644 index 08f107ab..00000000 --- a/reboot/aio/secrets.py +++ /dev/null @@ -1,137 +0,0 @@ -import os -import time -from abc import ABC, abstractmethod -from dataclasses import dataclass, replace -from pathlib import Path -from reboot.settings import ENVVAR_RBT_SECRETS_DIRECTORY -from typing import ClassVar, Optional - - -class SecretSource(ABC): - - @abstractmethod - async def get(self, secret_name: str) -> bytes: - """ - Get the secret value that has been stored for the given secret_name. - - Raises `SecretNotFoundException` if there is no secret with the given name. - """ - raise NotImplementedError() - - -class Secrets: - """Provides Reboot applications access to secrets.""" - - _static_secret_source: ClassVar[Optional[SecretSource]] = None - - def __init__(self): - super().__init__() - - self._secret_cache: dict[str, _CachedSecret] = dict() - self._secret_source: SecretSource - - if Secrets._static_secret_source is not None: - self._secret_source = Secrets._static_secret_source - return - - secrets_directory = os.environ.get(ENVVAR_RBT_SECRETS_DIRECTORY) - if secrets_directory is not None: - self._secret_source = DirectorySecretSource( - Path(secrets_directory) - ) - else: - self._secret_source = EnvironmentSecretSource() - - @classmethod - def set_secret_source(cls, secret_source: Optional[SecretSource]) -> None: - """Allows for overriding the default source of secrets, such as in unit tests. - - After a call to this method, all constructed `Secrets` instances will use the - given SecretSource, rather than accessing the Reboot Cloud. - """ - cls._static_secret_source = secret_source - - @property - def secret_source(self) -> SecretSource: - return self._secret_source - - async def get(self, secret_name: str, *, ttl_secs: float = 15.0) -> bytes: - """Get the secret value that has been stored for the given secret_name. - - If less than `ttl_secs` has elapsed since the last request for a secret, a cached value - may be returned to reduce traffic to the underlying source of secrets. Because the number - of secrets per application is expected to be static, the internal cache does not support - eviction. - - Raises `SecretNotFoundException` if there is no secret with the given name. - """ - # TODO: Should eventually allow for watching the secret value without polling. - now = time.time() - cached_secret = self._secret_cache.get(secret_name) - if cached_secret and cached_secret.cached_at + ttl_secs > now: - return cached_secret.value - - value = await self._secret_source.get(secret_name) - - # TODO: It is possible for multiple threads to race to put a value in the cache. - self._secret_cache[secret_name] = _CachedSecret(value, cached_at=now) - return value - - def adjust_entry_age_for_tests( - self, secret_name: str, *, age_delta: float - ) -> None: - entry = self._secret_cache[secret_name] - self._secret_cache[secret_name] = replace( - entry, cached_at=entry.cached_at + age_delta - ) - - -class SecretNotFoundException(Exception): - pass - - -@dataclass(frozen=True) -class DirectorySecretSource(SecretSource): - directory: Path - - async def get(self, secret_name: str) -> bytes: - path = self.directory / secret_name - if not path.exists(): - raise SecretNotFoundException( - f"No secret is stored for {secret_name=} (at `{path}`)." - ) - return path.read_bytes() - - -class EnvironmentSecretSource(SecretSource): - ENVIRONMENT_VARIABLE_PREFIX = "RBT_SECRET_" - - async def get(self, secret_name: str) -> bytes: - environment_variable_name = f"{self.ENVIRONMENT_VARIABLE_PREFIX}{secret_name.upper().replace('-', '_')}" - - value = os.environ.get(environment_variable_name) - if value is None: - raise SecretNotFoundException( - f"No environment variable was set for {secret_name=}; " - f"expected `{environment_variable_name}` to be set" - ) - return value.encode() - - -@dataclass(frozen=True) -class MockSecretSource(SecretSource): - secrets: dict[str, bytes] - - async def get(self, secret_name: str) -> bytes: - value = self.secrets.get(secret_name) - if value is None: - raise SecretNotFoundException( - f"No mock secret was stored for {secret_name=}." - ) - return value - - -@dataclass(frozen=True) -class _CachedSecret: - value: bytes - cached_at: float diff --git a/reboot/aio/state_managers.py b/reboot/aio/state_managers.py index c54f71ae..670a16ca 100644 --- a/reboot/aio/state_managers.py +++ b/reboot/aio/state_managers.py @@ -4,6 +4,7 @@ import bitarray # type: ignore[import] import grpc import hashlib +import inspect import log.log import logging import math @@ -2848,8 +2849,26 @@ async def loop( "to iterate" ) + completed_iteration = task_effect.iteration + + # Call iteration-complete callables BEFORE + # advancing and persisting the iteration + # counter, so that if a callable raises the + # exception propagates out of the iteration + # body and the entire workflow method gets + # retried -- including this iteration, since + # we haven't yet recorded its completion. The + # list is intentionally NOT cleared -- + # callables are persistent across iterations + # of the same method invocation and only get + # cleared when the method itself completes. + # Async callables are awaited to completion. + for callable in context._on_iteration_complete: + result = callable(iteration=completed_iteration) + if inspect.isawaitable(result): + await result + async with self._mutator_locks[state_type][state_ref]: - completed_iteration = task_effect.iteration task_effect.iteration += 1 await self._store( @@ -2919,7 +2938,26 @@ async def loop( context.loop = loop context.within_loop = lambda: within_loop.get() - yield self.complete_task + retrying = True + try: + yield self.complete_task + retrying = False + finally: + # The workflow method body has completed. Call + # method-complete callables with `retrying=` indicating + # whether the framework is about to retry, then clear + # *both* callable lists so any re-invocation on this same + # `WorkflowContext` starts with a fresh registration. If a + # callable raises, the exception propagates out of the + # workflow method body and the entire workflow method gets + # retried. + method_callables = context._on_method_complete + context._on_method_complete = [] + context._on_iteration_complete = [] + for callable in method_callables: + result = callable(retrying=retrying) + if inspect.isawaitable(result): + await result @asynccontextmanager async def transaction( @@ -3146,6 +3184,22 @@ async def check_for_idempotent_mutation( state_type_name = context.state_type_name state_ref = context._state_ref + # If there's an in-flight transaction for this state with a + # matching idempotency key, wait for it to finish rather than + # starting a duplicate. After it commits the normal check below + # will find the cached response. This avoids + # https://github.com/reboot-dev/mono/issues/5361. + transaction: Optional[StateManager.Transaction] + if isinstance(context, TransactionContext) and not context.nested: + transaction = self._participant_transactions[state_type_name].get( + state_ref + ) + if ( + transaction is not None and + transaction.idempotency_key == context.idempotency_key + ): + await transaction + # Recover idempotent mutations on demand if necessary. # # NOTE: we do this even if we are within a transaction because @@ -3181,8 +3235,8 @@ async def check_for_idempotent_mutation( # If we haven't found an idempotent mutation and we're within # a transaction, check and see if the transaction includes it. if idempotent_mutation is None and context.transaction_ids is not None: - transaction: Optional[StateManager.Transaction] = ( - self._participant_transactions[state_type_name].get(state_ref) + transaction = self._participant_transactions[state_type_name].get( + state_ref ) if ( diff --git a/reboot/aio/tests.py b/reboot/aio/tests.py index 32970953..db964c2a 100644 --- a/reboot/aio/tests.py +++ b/reboot/aio/tests.py @@ -41,6 +41,22 @@ def assert_called_twice_with( testcase.assertEqual(mock_obj.call_count, 2) +def temporary_environ( + testcase: unittest.TestCase, + values: dict[str, str], +) -> None: + """Set env vars for the duration of the test, restoring their + prior state (including removing keys that weren't set before) on + test cleanup. + + Call from `asyncSetUp` / `setUp` before anything that reads the + env vars runs. + """ + patcher = mock.patch.dict(os.environ, values) + patcher.start() + testcase.addCleanup(patcher.stop) + + class Reboot(reboot.aio.reboot.Reboot): """A testing specific version of `Reboot` that takes an `Application` instead of explicit keyword args.""" diff --git a/reboot/aio/workflows.py b/reboot/aio/workflows.py index b1189847..61607d62 100644 --- a/reboot/aio/workflows.py +++ b/reboot/aio/workflows.py @@ -1,4 +1,9 @@ +import collections.abc +import functools +import operator import sys +import types +import typing from reboot.aio.contexts import WorkflowContext from reboot.aio.idempotency import ( # noqa: F401 ALWAYS, @@ -7,6 +12,7 @@ How, ) from typing import ( + Any, Awaitable, Callable, Literal, @@ -14,12 +20,158 @@ Protocol, TypeAlias, TypeVar, - overload, ) T = TypeVar('T') +# Sentinel used as the default for `type=` on the public memoize-based +# primitives (`at_least_once` and friends) so we can tell "the caller +# passed `type=` explicitly" apart from "the caller omitted `type=` +# and we should infer it from the callable's return annotation". +class _Unset: + pass + + +_UNSET = _Unset() + +# Public alias used as the `type=` parameter on `at_least_once` / +# `at_most_once` / `until` / `until_changes` / `until_per_workflow` / +# `at_least_once_per_workflow` / `at_most_once_per_workflow` and the +# generated `write(...)` family. Aliased to `Any` because Python +# 3.10-3.13 has no public, stable spelling that covers everything we +# want to accept: plain classes (`int`, `Foo`), PEP 604 unions (`int | +# str`), `typing.Union[...]` + `typing.Optional[...]` (whose runtime +# type is the private `typing._UnionGenericAlias`), and parameterized +# generics (`list[int]`, `typing.List[int]`, `dict[str, X]`). Python +# 3.14 promotes `typing.Union` to a proper type that subsumes most of +# these -- once we move to 3.14 we can sharpen this to something like +# `type | types.UnionType | typing.Union | types.GenericAlias`. +# `_resolve_callable_return_type` (via `_normalize_type`) collapses +# whatever the caller passes down to `TypeT` before it reaches +# `memoize`, so internal code stays on the tight form. +Type: TypeAlias = Any + +# Internal post-normalization alias: what `memoize` and the runtime +# helpers (`_isinstance_type`, `_format_type`) see after +# `_normalize_type` has collapsed `typing.Union[...]` to PEP 604 and +# stripped parameterized generics down to their origin. +TypeT: TypeAlias = type | types.UnionType + + +def _normalize_type(t: Type) -> TypeT: + """Collapse whatever the caller passed as `type=` into the narrower + `TypeT = type | types.UnionType` so downstream code can rely on it + exclusively. + """ + # Need to use `Any` annotation for `origin` otherwise mypy's + # overload-driven narrowing of `typing.get_origin(t: Any)` picks + # `ParamSpec`-returning overloads and produces spurious + # `comparison-overlap` errors below. + origin: Any = typing.get_origin(t) + # `typing.Union[A, B, ...]` (and `typing.Optional[A]`, which + # is just `Union[A, None]`) are converted to PEP 604 unions + # via `functools.reduce(operator.or_, ...)`. + if origin is typing.Union: + return functools.reduce(operator.or_, typing.get_args(t)) + # Parameterized generics like `list[int]`, `typing.List[int]`, + # `dict[str, X]` are stripped to their origin (`list`, `dict`) + # since `isinstance` can't accept the parameterized form. We lose + # the parameter information, but the parameter would have been + # ignored by `isinstance` anyway. + if origin is not None and origin is not types.UnionType: + return origin + # Anything else (plain classes, PEP 604 unions) passes through + # unchanged. + return t + + +def _format_type(t: TypeT) -> str: + """Render a type for inclusion in an error message. Returns the + bare class name for plain classes (`int` -> `'int'`, + `app.foo.Foo` -> `'Foo'`) and recurses through PEP 604 unions + so custom-class members don't keep their module prefix: + `str(Foo | Bar)` is `'app.foo.Foo | app.bar.Bar'`, but + recursing produces `'Foo | Bar'`. + """ + if isinstance(t, type): + return t.__name__ + + # `TypeT` should otherwise only be a PEP 604 union type. + assert isinstance(t, types.UnionType) + return ' | '.join(_format_type(arg) for arg in typing.get_args(t)) + + +def _isinstance_type(t: TypeT) -> Any: + """Returns the "type" necessary for passing to `isinstance`. This is + necessary to both convert PEP 604 unions into tuples which + `isinstance` expects for "unions" as well as to remove + parameterized generics like `tuple[str, int]` which `isinstance` + rejects with "Subscripted generics cannot be used with class and + instance checks". Plain classes pass through unchanged. + """ + if isinstance(t, types.UnionType): + return tuple(_isinstance_type(arg) for arg in typing.get_args(t)) + # Parameterized generics like `list[int]` / `tuple[str, X]` can't + # be passed to `isinstance`. Strip down to the origin. + origin = typing.get_origin(t) + if origin is not None: + return origin + return t + + +def _resolve_callable_return_type( + callable: Callable[..., Any], + explicit: Type | _Unset, + *, + default: TypeT = type(None), +) -> tuple[TypeT, bool]: + """Decide what "type" `memoize()` should pass use to validate the + `callable`'s return value. If the caller passed `type=` + explicitly, honor it (after `_normalize_type` collapses it into + `TypeT`). Otherwise inspect `callable`'s return annotation and + fall back to `default` (typically `type(None)`, or `bool` for the + `until` family) if there isn't one. + + Returns `(resolved, inferred)` where `inferred=True` means we got + the type from the annotation rather than from the caller. + `memoize()` uses this flag to tailor the error message it raises + if the runtime value disagrees. + """ + if not isinstance(explicit, _Unset): + return _normalize_type(explicit), False + + try: + hints = typing.get_type_hints(callable) + except Exception: + # Forward references that don't resolve, builtins + # without annotations, etc. Fall back to the default. + return default, True + + annotation = hints.get("return", default) + + # `typing.Any` isn't a runtime type that `isinstance` can + # accept; treat it as `object` (which is "anything goes"). + if annotation is typing.Any: + return object, True + + # For an async function with signature `async def fn() -> T:`, + # `typing.get_type_hints` already returns `T` (not `Coroutine[Any, + # Any, T]`). But if a user *explicitly* annotated the return as + # `Coroutine[Any, Any, T]` or `Awaitable[T]` (legal at runtime + # even if mypy would reject it), `get_type_hints` returns the + # wrapper verbatim, and we need to just get `T`. + origin = typing.get_origin(annotation) + if origin in (collections.abc.Coroutine, collections.abc.Awaitable): + args = typing.get_args(annotation) + annotation = args[-1] if args else default + + if annotation is None: + return type(None), True + + return _normalize_type(annotation), True + + class Memoize(Protocol[T]): async def __call__( @@ -32,7 +184,8 @@ async def __call__( context: WorkflowContext, callable: Callable[[], Awaitable[T]], *, - type_t: type[T], + type_t: TypeT, + type_t_inferred: bool, at_most_once: bool, until: bool = False, retryable_exceptions: Optional[list[type[Exception]]] = None, @@ -61,52 +214,36 @@ class AtMostOnceFailedBeforeCompleting(Exception): ] -@overload -async def at_most_once( - idempotency_alias_or_tuple: str | AtMostLeastOnceTupleType, - context: WorkflowContext, - callable: Callable[[], Awaitable[None]], - *, - type: type = type(None), - retryable_exceptions: Optional[list[type[Exception]]] = None, -) -> None: - ... - - -@overload -async def at_most_once( - idempotency_alias_or_tuple: str | AtMostLeastOnceTupleType, - context: WorkflowContext, - callable: Callable[[], Awaitable[T]], - *, - type: type[T], - retryable_exceptions: Optional[list[type[Exception]]] = None, -) -> T: - ... - - async def at_most_once( idempotency_alias_or_tuple: str | AtMostLeastOnceTupleType, context: WorkflowContext, callable: Callable[[], Awaitable[T]], *, - type: type = type(None), + type: Type | _Unset = _UNSET, retryable_exceptions: Optional[list[type[Exception]]] = None, ) -> T: """Attempts to run and memoize the result of calling `callable` but only once. + `type=` is optional: when omitted, `callable`'s return + annotation is used. Falls back to `type(None)` if there's + no annotation -- the runtime check in `memoize` will raise + a clear error if the callable actually returns a non-`None` + value in that case. + NOTE: this is the Python wrapper for `reboot.memoize.v1` and as such uses `pickle` to serialize the result of calling `callable` which therefore must be pickle-able. """ assert memoize is not None + type_t, type_t_inferred = _resolve_callable_return_type(callable, type) try: return await memoize( idempotency_alias_or_tuple, context, callable, - type_t=type, + type_t=type_t, + type_t_inferred=type_t_inferred, at_most_once=True, retryable_exceptions=retryable_exceptions, ) @@ -122,36 +259,12 @@ async def at_most_once( raise -@overload -async def at_most_once_per_workflow( - idempotency_alias: str, - context: WorkflowContext, - callable: Callable[[], Awaitable[None]], - *, - type: type = type(None), - retryable_exceptions: Optional[list[type[Exception]]] = None, -) -> None: - ... - - -@overload async def at_most_once_per_workflow( idempotency_alias: str, context: WorkflowContext, callable: Callable[[], Awaitable[T]], *, - type: type[T], - retryable_exceptions: Optional[list[type[Exception]]] = None, -) -> T: - ... - - -async def at_most_once_per_workflow( - idempotency_alias: str, - context: WorkflowContext, - callable: Callable[[], Awaitable[T]], - *, - type: type = type(None), + type: Type | _Unset = _UNSET, retryable_exceptions: Optional[list[type[Exception]]] = None, ) -> T: """Syntactic sugar for calling without an idempotency tuple.""" @@ -164,81 +277,45 @@ async def at_most_once_per_workflow( ) -@overload -async def at_least_once( - idempotency_alias_or_tuple: str | AtMostLeastOnceTupleType, - context: WorkflowContext, - callable: Callable[[], Awaitable[None]], - *, - type: type = type(None), -) -> None: - ... - - -@overload async def at_least_once( idempotency_alias_or_tuple: str | AtMostLeastOnceTupleType, context: WorkflowContext, callable: Callable[[], Awaitable[T]], *, - type: type[T], -) -> T: - ... - - -async def at_least_once( - idempotency_alias_or_tuple: str | AtMostLeastOnceTupleType, - context: WorkflowContext, - callable: Callable[[], Awaitable[T]], - *, - type: type = type(None), + type: Type | _Unset = _UNSET, ) -> T: """Attempts to run and memoize the result of calling `callable` while supporting retrying as many times as necessary until `callable` succeeds. + `type=` is optional: when omitted, `callable`'s return + annotation is used. Falls back to `type(None)` if there's + no annotation -- the runtime check in `memoize` will raise + a clear error if the callable actually returns a non-`None` + value in that case. + NOTE: this is the Python wrapper for `reboot.memoize.v1` and as such uses `pickle` to serialize the result of calling `callable` which therefore must be pickle-able. """ assert memoize is not None + type_t, type_t_inferred = _resolve_callable_return_type(callable, type) return await memoize( idempotency_alias_or_tuple, context, callable, - type_t=type, + type_t=type_t, + type_t_inferred=type_t_inferred, at_most_once=False, ) -@overload -async def at_least_once_per_workflow( - idempotency_alias: str, - context: WorkflowContext, - callable: Callable[[], Awaitable[None]], - *, - type: type = type(None), -) -> None: - ... - - -@overload -async def at_least_once_per_workflow( - idempotency_alias: str, - context: WorkflowContext, - callable: Callable[[], Awaitable[T]], - *, - type: type[T], -) -> T: - ... - - async def at_least_once_per_workflow( idempotency_alias: str, context: WorkflowContext, callable: Callable[[], Awaitable[T]], *, - type: type = type(None), + type: Type | _Unset = _UNSET, ) -> T: """Syntactic sugar for calling without an idempotency tuple.""" return await at_least_once( @@ -255,82 +332,48 @@ async def at_least_once_per_workflow( ] -@overload -async def until( - idempotency_alias_or_tuple: str | UntilTupleType, - context: WorkflowContext, - callable: Callable[[], Awaitable[bool]], - *, - type: type = bool, -) -> bool: - ... - - -@overload async def until( idempotency_alias_or_tuple: str | UntilTupleType, context: WorkflowContext, callable: Callable[[], Awaitable[bool | T]], *, - type: type[T], + type: Type | _Unset = _UNSET, ) -> T: - ... - - -async def until( - idempotency_alias_or_tuple: str | UntilTupleType, - context: WorkflowContext, - callable: Callable[[], Awaitable[bool | T]], - *, - type: type = bool, -) -> bool | T: """Attempts to reactively run `callable` "until" it returns a non `False` result we memoize. + + `type=` is optional: when omitted, `callable`'s return + annotation is used (with `bool` as the fallback when there + isn't one, since the conventional shape for `until` is + `Callable[[], Awaitable[bool]]`). """ async def converge(): return await context.retry_reactively_until(callable) + type_t, type_t_inferred = _resolve_callable_return_type( + callable, type, default=bool + ) + assert memoize is not None return await memoize( idempotency_alias_or_tuple, context, converge, - type_t=type, + type_t=type_t, + type_t_inferred=type_t_inferred, at_most_once=False, until=True, ) -@overload -async def until_per_workflow( - idempotency_alias: str, - context: WorkflowContext, - callable: Callable[[], Awaitable[bool]], - *, - type: type = bool, -) -> bool: - ... - - -@overload async def until_per_workflow( idempotency_alias: str, context: WorkflowContext, callable: Callable[[], Awaitable[bool | T]], *, - type: type[T], + type: Type | _Unset = _UNSET, ) -> T: - ... - - -async def until_per_workflow( - idempotency_alias: str, - context: WorkflowContext, - callable: Callable[[], Awaitable[bool | T]], - *, - type: type = bool, -) -> bool | T: return await until( (idempotency_alias, PER_WORKFLOW), context, @@ -344,17 +387,26 @@ async def until_changes( context: WorkflowContext, callable: Callable[[], Awaitable[T]], *, - type: type[T], + type: Type | _Unset = _UNSET, equals: Callable[[T, T], bool] = lambda previous, current: previous == current, ) -> T: """Runs `callable` at each iteration, only returning if the result of - running callable != the result from the previous iteration.""" + running callable != the result from the previous iteration. + + `type=` is optional: when omitted, `callable`'s return + annotation is used. + """ if not context.within_loop(): raise RuntimeError( "Waiting for changes must be done _within_ a control loop" ) + # Resolve `type=` once at the top so the two `until_per_workflow` + # calls below all use the same key, whether passed explicitly + # by the caller or inferred from `callable`'s return annotation. + type_t, _ = _resolve_callable_return_type(callable, type) + iteration = context.task.iteration previous: Optional[T] = None @@ -370,7 +422,7 @@ async def missing_memoized_value() -> T: f"{idempotency_alias} #{iteration - 1}", context, missing_memoized_value, - type=type, + type=type_t, ) # Wait until previous result does not equal current result. @@ -387,5 +439,5 @@ async def previous_not_equals_current(): f"{idempotency_alias} #{iteration}", context, previous_not_equals_current, - type=type, + type=type_t, ) diff --git a/reboot/api.py b/reboot/api.py index 64b87569..0b40ff18 100644 --- a/reboot/api.py +++ b/reboot/api.py @@ -46,7 +46,7 @@ # `None` is allowed for `Optional` fields, but we check that separately. } -ALLOWED_DEFAULT_FACTORY_BY_FIELD_TYPE = { +ALLOWED_DEFAULT_FACTORY_BY_FIELD_TYPE: dict[type, type] = { list: list, dict: dict, } @@ -82,6 +82,8 @@ def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: # type like `list` or `dict`. field_type_origin = field_info.annotation + assert field_type_origin is not None + if field_info.default_factory is not None: allowed_default_factory = ALLOWED_DEFAULT_FACTORY_BY_FIELD_TYPE.get( field_type_origin, @@ -229,7 +231,7 @@ def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: def _get_discriminated_union_info( field_type: type, field_info: Optional[FieldInfo] = None, -) -> Optional[Tuple[str, list[Model]]]: +) -> Optional[Tuple[str, list[type[Model]]]]: """ Check if a field type is a discriminated union and return info about it. @@ -249,6 +251,11 @@ def _get_discriminated_union_info( if discriminator is None: return None + # Reboot only supports the string-name form of pydantic + # discriminators (the field name); callable `Discriminator` + # instances aren't supported. + assert isinstance(discriminator, str) + # Get all non-None args, since it might be an optional discriminated union. non_none_args = [ arg for arg in get_args(field_type) if arg is not type(None) @@ -880,7 +887,7 @@ class MethodModel(pydantic.BaseModel): description: Optional[str] = None factory: bool = False kind: MethodKind - mcp: Union[Tool, Literal[False], None] = None + mcp: Optional[Tool] # TODO(rjh): we need more experience with resources # in the context of AI Chat apps before @@ -1216,20 +1223,14 @@ def __init__(self, **types: Optional[Type]): request=None, response=None, factory=True, + # The `User.create` method is reserved for auto + # construction a `User` state for new AI session. It + # is called by the Reboot internally and shouldn't + # be exposed as an MCP tool so others can't call it + # directly. + mcp=None, ) - # Auto-enable MCP for the auto-constructed state - # type's methods that don't explicitly opt out. - # The auto-constructor is never exposed over MCP. - # UI methods are already MCP-only; skip them. - for method_name, method in (data_type.methods.items()): - if method_name == AUTO_CONSTRUCT_METHOD: - continue - if isinstance(method, UI): - continue - if method.mcp is None: - method.mcp = Tool() - # Only pass non-None types to Pydantic. super().__init__(**{k: v for k, v in types.items() if v is not None}) diff --git a/reboot/benchmarks/construct/.rbtrc b/reboot/benchmarks/construct/.rbtrc index 433046cf..dea18c74 100644 --- a/reboot/benchmarks/construct/.rbtrc +++ b/reboot/benchmarks/construct/.rbtrc @@ -2,8 +2,8 @@ generate --nodejs=api/ generate api/ dev run --no-chaos -dev run --name=benchmarks +dev run --application-name=benchmarks dev run --nodejs dev run --application=main.ts -dev expunge --name=benchmarks +dev expunge --application-name=benchmarks diff --git a/reboot/benchmarks/construct/api/.rbtrc b/reboot/benchmarks/construct/api/.rbtrc index 433046cf..dea18c74 100644 --- a/reboot/benchmarks/construct/api/.rbtrc +++ b/reboot/benchmarks/construct/api/.rbtrc @@ -2,8 +2,8 @@ generate --nodejs=api/ generate api/ dev run --no-chaos -dev run --name=benchmarks +dev run --application-name=benchmarks dev run --nodejs dev run --application=main.ts -dev expunge --name=benchmarks +dev expunge --application-name=benchmarks diff --git a/reboot/benchmarks/construct/package-lock.json b/reboot/benchmarks/construct/package-lock.json index e3eade00..a73bc278 100644 --- a/reboot/benchmarks/construct/package-lock.json +++ b/reboot/benchmarks/construct/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "@bufbuild/protobuf": "1.10.1", - "@reboot-dev/reboot": "0.46.0", + "@reboot-dev/reboot": "1.0.3", "@supercharge/promise-pool": "^3.2.0", "parse-duration": "2.1.3", "uuid": "11.1.0" @@ -507,15 +507,15 @@ } }, "node_modules/@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -544,9 +544,9 @@ } }, "node_modules/@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0", @@ -3092,13 +3092,13 @@ "optional": true }, "@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "requires": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -3115,9 +3115,9 @@ } }, "@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "requires": { "@scarf/scarf": "1.4.0", "typescript": "5.4.5", diff --git a/reboot/benchmarks/construct/package.json b/reboot/benchmarks/construct/package.json index eb5f0fff..cd623515 100644 --- a/reboot/benchmarks/construct/package.json +++ b/reboot/benchmarks/construct/package.json @@ -11,7 +11,7 @@ "type": "module", "dependencies": { "@bufbuild/protobuf": "1.10.1", - "@reboot-dev/reboot": "0.46.0", + "@reboot-dev/reboot": "1.0.3", "@supercharge/promise-pool": "^3.2.0", "uuid": "11.1.0", "parse-duration": "2.1.3" diff --git a/reboot/cli/BUILD.bazel b/reboot/cli/BUILD.bazel index a9619b9f..cb6af589 100644 --- a/reboot/cli/BUILD.bazel +++ b/reboot/cli/BUILD.bazel @@ -12,29 +12,6 @@ py_library( ], ) -py_library( - name = "cloud_py", - srcs = ["cloud.py"], - srcs_version = "PY3", - visibility = ["//visibility:public"], - deps = [ - ":commands_py", - ":rc_py", - ":terminal_py", - "//reboot:api_keys_py", - "@com_github_reboot_dev_reboot//rbt/cloud/v1alpha1/application:application_py_reboot", - "@com_github_reboot_dev_reboot//rbt/cloud/v1alpha1/auth:auth_py_reboot", - "@com_github_reboot_dev_reboot//rbt/cloud/v1alpha1/organizations:organizations_py_reboot", - "@com_github_reboot_dev_reboot//rbt/v1alpha1:errors_py_proto", - "@com_github_reboot_dev_reboot//reboot:naming_py", - "@com_github_reboot_dev_reboot//reboot:settings_py", - "@com_github_reboot_dev_reboot//reboot:time_py", - "@com_github_reboot_dev_reboot//reboot/aio:aborted_py", - "@com_github_reboot_dev_reboot//reboot/aio:external_py", - "@com_github_reboot_dev_reboot//reboot/aio/backoff:python", - ], -) - py_library( name = "commands_py", srcs = ["commands.py"], @@ -66,7 +43,6 @@ py_library( requirement("aiofiles"), requirement("pyprctl"), requirement("cryptography"), - ":cloud_py", ":directories_py", ":generate_py", ":monkeys_py", @@ -153,19 +129,6 @@ py_library( ], ) -py_library( - name = "secret_py", - srcs = ["secret.py"], - srcs_version = "PY3", - visibility = ["//visibility:public"], - deps = [ - ":cloud_py", - ":rc_py", - ":terminal_py", - "@com_github_reboot_dev_reboot//reboot:settings_py", - ], -) - py_library( name = "watch_py", srcs = ["watch.py"], @@ -255,14 +218,17 @@ py_library( srcs_version = "PY3", visibility = ["//visibility:public"], deps = [ - ":cloud_py", ":dev_py", ":export_import_py", ":generate_py", ":rc_py", - ":secret_py", ":serve_py", ":task_py", + "//reboot/cli/cloud:__init___py", + "//reboot/cli/cloud:down_py", + "//reboot/cli/cloud:logs_py", + "//reboot/cli/cloud:secrets_py", + "//reboot/cli/cloud:up_py", "//reboot/cli/init:init_py", ], ) diff --git a/reboot/cli/cli.py b/reboot/cli/cli.py index 5b34dc84..9ad69d2e 100644 --- a/reboot/cli/cli.py +++ b/reboot/cli/cli.py @@ -2,20 +2,39 @@ import reboot.cli.terminal as terminal import sys from pathlib import Path -from reboot.cli.cloud import cloud_down, cloud_logs, cloud_up, register_cloud -from reboot.cli.dev import dev_expunge, dev_run, register_dev +from reboot.cli.cloud import ( + cloud_subcommands, + handle_cloud_subcommand, + register_cloud, +) +from reboot.cli.dev import dev_subcommands, handle_dev_subcommand, register_dev from reboot.cli.export_import import ( - do_export, - do_import, + export_and_import_subcommands, + handle_export_and_import_subcommand, register_export_and_import, ) -from reboot.cli.generate import generate, register_generate -from reboot.cli.init.init import init_run, register_init +from reboot.cli.generate import ( + generate_subcommands, + handle_generate_subcommand, + register_generate, +) +from reboot.cli.init.init import ( + handle_init_subcommand, + init_subcommands, + register_init, +) from reboot.cli.rc import ArgumentParser -from reboot.cli.secret import register_secret, secret_delete, secret_write -from reboot.cli.serve import register_serve, serve_run +from reboot.cli.serve import ( + handle_serve_subcommand, + register_serve, + serve_subcommands, +) from reboot.cli.subprocesses import Subprocesses -from reboot.cli.task import register_task, task_cancel, task_list +from reboot.cli.task import ( + handle_task_subcommand, + register_task, + task_subcommands, +) from typing import Optional @@ -41,33 +60,21 @@ def create_parser( parser = ArgumentParser( program='rbt', filename='.rbtrc', - subcommands=[ - 'cloud down', - 'cloud up', - 'cloud secret delete', - 'cloud secret write', - 'cloud logs', - 'dev expunge', - 'dev run', - 'export', - 'import', - 'init', - 'generate', - 'serve run', - 'task list', - 'task cancel', - ], + subcommands=( + cloud_subcommands() + dev_subcommands() + + export_and_import_subcommands() + generate_subcommands() + + init_subcommands() + serve_subcommands() + task_subcommands() + ), rc_file=rc_file, argv=argv, ) add_global_options(parser) + register_cloud(parser) register_dev(parser) register_export_and_import(parser) register_generate(parser) - register_secret(parser) - register_cloud(parser) register_init(parser) register_serve(parser) register_task(parser) @@ -99,50 +106,40 @@ async def cli() -> int: args, argv_after_dash_dash = parser.parse_args() - if args.subcommand == 'dev run': - return await dev_run( + if (result := await handle_cloud_subcommand(args)) is not None: + return result + elif ( + result := await handle_dev_subcommand( args, parser=parser, parser_factory=lambda argv: create_parser(argv=argv), ) - elif args.subcommand == 'dev expunge': - await dev_expunge(args, parser) - return 0 - elif args.subcommand == 'export': - return await do_export(args) - elif args.subcommand == 'import': - return await do_import(args) - elif args.subcommand == 'generate': - return await generate(args, argv_after_dash_dash, parser=parser) - elif args.subcommand == 'secret write': - await secret_write(args) - return 0 - elif args.subcommand == 'secret delete': - await secret_delete(args) - return 0 - elif args.subcommand == 'cloud up': - return await cloud_up(args) - elif args.subcommand == 'cloud down': - await cloud_down(args) - return 0 - elif args.subcommand == 'cloud logs': - await cloud_logs(args) - return 0 - elif args.subcommand == 'init': - await init_run(args) - return 0 - elif args.subcommand == 'serve run': - return await serve_run( + ) is not None: + return result + elif ( + result := await handle_export_and_import_subcommand(args) + ) is not None: + return result + elif ( + result := await handle_generate_subcommand( + args, + argv_after_dash_dash=argv_after_dash_dash, + parser=parser, + ) + ) is not None: + return result + elif (result := await handle_init_subcommand(args)) is not None: + return result + elif ( + result := await handle_serve_subcommand( args, parser=parser, parser_factory=lambda argv: create_parser(argv=argv), ) - elif args.subcommand == 'task list': - await task_list(args) - return 0 - elif args.subcommand == 'task cancel': - await task_cancel(args) - return 0 + ) is not None: + return result + elif (result := await handle_task_subcommand(args)) is not None: + return result raise NotImplementedError( f"Subcommand '{args.subcommand}' is not implemented" diff --git a/reboot/cli/cloud.py b/reboot/cli/cloud.py deleted file mode 100644 index d2c0f97e..00000000 --- a/reboot/cli/cloud.py +++ /dev/null @@ -1,1043 +0,0 @@ -import argparse -import base64 -import json -import tempfile -import traceback -from enum import Enum -from pathlib import Path -from rbt.cloud.v1alpha1.application.application_pb2 import ( - ApplicationSize, - ConcurrentModificationError, - InvalidInputError, - PaymentMethodRequiredError, - Status, -) -from rbt.cloud.v1alpha1.application.application_rbt import Application -from rbt.cloud.v1alpha1.auth.auth_rbt import APIKey -from rbt.cloud.v1alpha1.logs.logs_pb2 import LogsRequest, Source -from rbt.cloud.v1alpha1.organizations.organizations_rbt import Organizations -from rbt.v1alpha1.errors_pb2 import ( - PermissionDenied, - StateAlreadyConstructed, - StateNotConstructed, - Unauthenticated, - Unavailable, -) -from reboot.aio.aborted import Aborted -from reboot.aio.backoff import Backoff -from reboot.aio.external import ExternalContext -from reboot.aio.types import ApplicationId -from reboot.api_keys import ( - InvalidAPIKeyBearerToken, - parse_api_key_bearer_token, -) -from reboot.cli import terminal -from reboot.cli.commands import run_command -from reboot.cli.rc import ArgumentParser, SubcommandParser -from reboot.naming import ( - ORGANIZATIONS_ID, - OrganizationId, - OrganizationName, - QualifiedApplicationName, - UserId, - make_qualified_application_name_from_owner_id_and_application_name, -) -from reboot.time import DateTimeWithTimeZone -from typing import Optional - -DEFAULT_REBOOT_CLOUD_URL = "https://cloud.prod1.rbt.cloud:9991" - -_API_KEY_FLAG = '--api-key' - -SIZE_NAME_TO_ENUM = { - 'xsmall': ApplicationSize.XSMALL, - 'small': ApplicationSize.SMALL, - 'medium': ApplicationSize.MEDIUM, - 'large': ApplicationSize.LARGE, - 'xlarge': ApplicationSize.XLARGE, -} - -VALID_SIZES = list(SIZE_NAME_TO_ENUM.keys()) - - -class SourceType(Enum): - SERVER = 'server' - CONFIG = 'config' - ALL = 'all' - - -def add_cloud_options(subcommand: SubcommandParser, *, api_key_required: bool): - """Add flags common to all `rbt` commands that interact with the cloud.""" - # TODO: Consider moving these to flags on the `cloud` subcommand using #3845 - - subcommand.add_argument( - '--cloud-url', - type=str, - help="the URL of the Reboot cloud API", - default=DEFAULT_REBOOT_CLOUD_URL, - non_empty_string=True, - ) - # TODO: This should probably be read from a file by default. - subcommand.add_argument( - _API_KEY_FLAG, - type=str, - help="the API key to use to connect to the Reboot Cloud API", - default=None, - required=api_key_required, - non_empty_string=True, - ) - subcommand.add_argument( - '--organization', - type=str, - help="the organization to use for the application", - default=None, - non_empty_string=True, - ) - - -def _application_url(application_id: ApplicationId, cloud_url: str) -> str: - """ - Given a cloud URL (e.g. `https://cloud.prod1.rbt.cloud:9991`), returns the - url for the given application (e.g. `https://a12345.prod1.rbt.cloud:9991`). - """ - if not ( - cloud_url.startswith("https://") or cloud_url.startswith("http://") - ): - terminal.fail( - f"Cloud URL '{cloud_url}' must have 'https://' or 'http://'." - ) - protocol, hostname_port = cloud_url.split("://", maxsplit=1) - if not hostname_port.startswith("cloud."): - terminal.fail( - f"Cloud host '{hostname_port}' is missing expected 'cloud.' prefix" - ) - cell_hostname_port = hostname_port.removeprefix("cloud.") - return f"{protocol}://{application_id}.{cell_hostname_port}" - - -def register_cloud(parser: ArgumentParser): - """Register the 'cloud' subcommand with the given parser.""" - - def _add_common_flags(subcommand: SubcommandParser): - """Adds flags common to every `rbt cloud` subcommand.""" - - add_cloud_options(subcommand, api_key_required=True) - - subcommand.add_argument( - '--name', - type=str, - required=True, - help="name of the application", - non_empty_string=True, - ) - - up_subcommand = parser.subcommand('cloud up') - _add_common_flags(up_subcommand) - up_subcommand.add_argument( - '--dockerfile', - type=Path, - help='the Dockerfile to build this application from', - default='./Dockerfile', - ) - up_subcommand.add_argument( - '--docker-build-arg', - type=str, - repeatable=True, - help='additional build arguments to pass to the Docker build command. ' - 'Can be specified multiple times, e.g. ' - '`--docker-build-arg=key1=value1 --docker-build-arg=key2`', - ) - up_subcommand.add_argument( - '--size', - type=str, - choices=VALID_SIZES, - default='xsmall', - help='the size of the application', - ) - down_subcommand = parser.subcommand('cloud down') - _add_common_flags(down_subcommand) - down_subcommand.add_argument( - '--expunge', - type=bool, - required=True, - help='if true, expunge all application state when bringing ' - 'the application down', - ) - - logs_subcommand = parser.subcommand('cloud logs') - _add_common_flags(logs_subcommand) - - logs_subcommand.add_argument( - '--follow', - type=bool, - default=False, - help='if true, follows the logs as they are produced', - ) - - # Filters. - logs_subcommand.add_argument( - '--revisions', - type=str, - # We default to ">=latest" rather than "latest" because the user - # may also use "--follow", in which case they presumably want to - # see all logs from the latest revision _and_ any that are upped - # in the future. If they really don't want to follow future - # revisions they can specify just "latest" manually. - default=">=latest", - help='the revision number(s) to get logs for. ' - 'May specify a single number (e.g. "4"), several comma-separated ' - 'numbers (e.g. "4,7"), or a from-range using ">=" (e.g. ">=4" for logs ' - 'at and beyond revision 4). ' - 'May use "latest" to replace a number. ' - 'Default: ">=latest".', - ) - logs_subcommand.add_argument( - '--source', - type=str, - choices=[value.name.lower() for value in SourceType], - # We default to `server` because, under normal circumstances, - # developers don't care about the logs of their config runs. If a config - # run fails, its `rbt cloud up` will already print those logs. If the - # config run succeeds, its logs are mostly irrelevant. - default=SourceType.SERVER.name.lower(), - help="which sources to show logs from; defaults to " - f"'{SourceType.SERVER.name.lower()}'", - ) - logs_subcommand.add_argument( - '--last', - type=int, - default=None, - help='if set, only show the last N log lines. If not set, show all ' - 'log lines.', - ) - - # Presentation. - logs_subcommand.add_argument( - '--show-revision', - type=bool, - default=None, - help='if true, show the revision number of the log line in the logs. ' - 'If false, never do so. If unset, will show revision numbers if there ' - 'may be more than one revision in the logs.', - ) - logs_subcommand.add_argument( - '--show-source', - type=bool, - default=None, - help='if true, show information about the source of the log ' - 'line in the logs. If false, never do so. If unset, will show source ' - 'information if there may be more than one type of source in the logs.', - ) - logs_subcommand.add_argument( - '--show-timestamp', - type=bool, - default=False, - help='if true, includes timestamp in the logs', - ) - - -async def _user_id_from_api_key(api_key: str, cloud_url: str) -> str: - try: - api_key_id, api_key_secret = parse_api_key_bearer_token(token=api_key) - except InvalidAPIKeyBearerToken: - # Note that we do not log the API key contents; they are a secret, which - # we don't want to output to a log file (if any). - terminal.fail( - "Invalid API key shape (expected: " - "'XXXXXXXXXX-XXXXXXXXXXXXXXXXXXXX')" - ) - - context = ExternalContext( - name="user-id-from-api-key", - url=cloud_url, - # TODO(rjh): once APIKey reads the bearer token for `Authenticate`, use - # that instead of passing `secret` in the proto below. - ) - - try: - return ( - await APIKey.ref(api_key_id).Authenticate( - context, - secret=api_key_secret, - ) - ).user_id - except Aborted as aborted: - match aborted.error: - case StateNotConstructed( # type: ignore[misc] - ) | PermissionDenied( # type: ignore[misc] - ) | Unauthenticated(): # type: ignore[misc] - # Note that we do not log the API key contents; they - # are a secret, which we don't want to output to a log - # file (if any). - terminal.fail("Invalid API key") - case _: - terminal.fail(f"Unexpected error: {aborted}") - - -async def _get_organization_id( - *, - user_id: UserId, - organization_name: OrganizationName, - cloud_url: str, - api_key: str, -) -> Optional[OrganizationId]: - # Validate the organization name is nonempty. - if organization_name.strip() == "": - terminal.fail("Invalid organization, got empty string") - - context = ExternalContext( - name="get-organization-id", - url=cloud_url, - bearer_token=api_key, - ) - - organizations = Organizations.ref(ORGANIZATIONS_ID) - try: - response = await organizations.resolve( - context, - organization_name=organization_name, - ) - except Aborted as aborted: - match aborted.error: - case _: - # Not expecting any errors; invariant here is that - # we've already authenticated the API key because we - # have a user ID (which we can only get from - # authenticating the API key). - traceback.print_exc() - terminal.fail("Please report this bug to the maintainers") - else: - if response.HasField("organization_id"): - return response.organization_id - return None - - -async def _maybe_create_application( - qualified_application_name: QualifiedApplicationName, - cloud_url: str, - api_key: str, -) -> None: - """ - Creates the Application with the given `qualified_application_name` if it - doesn't exist yet. - """ - # Use a separate context for `Create()`, since that call is allowed to fail - # and will then leave its context unable to continue due to idempotency - # uncertainty. - context = ExternalContext( - name="cloud-up-create-application", - url=cloud_url, - bearer_token=api_key, - ) - try: - await Application.Create(context, qualified_application_name) - except Aborted as aborted: - match aborted.error: - case StateAlreadyConstructed(): # type: ignore[misc] - # That's OK; we just want the application to exist! - pass - case _: - # Unexpected error, propagate it. - raise - - -async def _does_application_exist( - *, - qualified_application_name: QualifiedApplicationName, - cloud_url: str, - api_key: str, -) -> bool: - """ - Checks if the `Application` with the given - `qualified_application_name` exists. - """ - # Use a separate context since that call is allowed to fail and - # will then leave its context unable to continue due to - # idempotency uncertainty. - context = ExternalContext( - name="cloud-application-status", - url=cloud_url, - bearer_token=api_key, - ) - try: - await Application.ref(qualified_application_name).status(context) - except Aborted as aborted: - match aborted.error: - case StateNotConstructed(): # type: ignore[misc] - return False - case _: - # Unexpected error, propagate it. - raise - return True - - -def parse_revisions( - revisions: str, -) -> list[LogsRequest.RevisionFilter]: - """ - Parse a revisions string (e.g. "0, >=4") - - Parses these into `LogsRequest.RevisionFilter` protos, which can be - sent to the Reboot Cloud. - """ - original_revisions = revisions - - filters: list[LogsRequest.RevisionFilter] = [] - - for revision in revisions.split(","): - revision = revision.strip() - operator: LogsRequest.RevisionFilter.Operator.ValueType = LogsRequest.RevisionFilter.Operator.EQUAL - right_hand_revision_number: Optional[int] = None - right_hand_latest: Optional[bool] = None - - if revision.startswith(">="): - revision = revision.removeprefix(">=") - operator = LogsRequest.RevisionFilter.Operator.GREATER_THAN_OR_EQUAL - elif revision.startswith("="): - # Equal is the default operator; simply remove the prefix. - # Support both `=` and `==` as prefixes. - revision = revision.removeprefix("=") - revision = revision.removeprefix("=") - - if revision == "latest": - right_hand_latest = True - else: - try: - right_hand_revision_number = int(revision) - except ValueError: - terminal.fail( - f"Invalid revision number '{revision}' in " - f"--revision='{original_revisions}'; expected a " - "number or 'latest'." - ) - - filter = LogsRequest.RevisionFilter() - filter.operator = operator - if right_hand_latest is not None: - filter.right_hand_latest = right_hand_latest - else: - assert right_hand_revision_number is not None - filter.right_hand_revision_number = right_hand_revision_number - - filters.append(filter) - - return filters - - -def _may_select_multiple_revisions( - revision_filters: list[LogsRequest.RevisionFilter], - follow: bool, -) -> bool: - """ - Returns True if the `revisions` may pass logs for multiple revisions. - """ - assert len(revision_filters) > 0, "Must have at least one revision filter" - if len(revision_filters) > 1: - # TODO: it's possible for a user to write e.g. "0,0", and - # ideally we'd understand that that's a single revision, - # but it's not a big deal. - return True - - # Special case: ">=latest" is a single revision, EXCEPT if we're - # following. - revision_filter = revision_filters[0] - if ( - revision_filter.operator - == LogsRequest.RevisionFilter.Operator.GREATER_THAN_OR_EQUAL and - revision_filter.right_hand_latest - ): - return follow - - # Otherwise, it's down to the operator. - return revision_filter.operator != LogsRequest.RevisionFilter.Operator.EQUAL - - -async def _parse_common_cloud_args( - args: argparse.Namespace -) -> tuple[UserId, QualifiedApplicationName, Optional[OrganizationName]]: - user_id = await _user_id_from_api_key( - api_key=args.api_key, - cloud_url=args.cloud_url, - ) - - # If --organization was specified (which is not required for - # backwards compatibility) then get its ID. This also validates - # that the organization exists which provides a better experience - # for our users that might have just misspelled it. - organization_name: Optional[OrganizationName] = args.organization - organization_id: Optional[OrganizationId] = None - if organization_name is not None: - organization_id = await _get_organization_id( - user_id=user_id, - organization_name=organization_name, - cloud_url=args.cloud_url, - api_key=args.api_key, - ) - if organization_id is None: - terminal.fail(f"Organization '{organization_name}' does not exist") - - qualified_application_name = ( - make_qualified_application_name_from_owner_id_and_application_name( - owner_id=organization_id or user_id, - application_name=args.name, - ) - ) - - return user_id, qualified_application_name, organization_name - - -async def cloud_up(args: argparse.Namespace) -> int: - """Implementation of the 'cloud up' subcommand.""" - - user_id, qualified_application_name, organization_name = ( - await _parse_common_cloud_args(args) - ) - - # If the application does not yet exist then --org is required! - if organization_name is None and not await _does_application_exist( - qualified_application_name=qualified_application_name, - cloud_url=args.cloud_url, - api_key=args.api_key, - ): - terminal.fail("--organization=... is required for new applications") - - context = ExternalContext( - name="cloud-up", - url=args.cloud_url, - bearer_token=args.api_key, - ) - - try: - terminal.info("[😇] checking permissions...", end=" ") - await _maybe_create_application( - qualified_application_name=qualified_application_name, - cloud_url=args.cloud_url, - api_key=args.api_key, - ) - application = Application.ref(qualified_application_name) - - pushinfo_response = await application.PushInfo(context) - terminal.info("✅") - - registry_endpoint = ( - pushinfo_response.registry_url - # Regardless of whether the prefix is "https" or "http", we must - # remove it; the Docker client will decide for itself whether it - # believes the registry is "secure" or "insecure". We hope it - # guesses right, otherwise the requests will fail. - .removeprefix("https://").removeprefix("http://") - ) - docker_tag = f"{registry_endpoint}/{pushinfo_response.repository}:{pushinfo_response.tag}" - - digest = await _docker_build_and_push( - dockerfile=args.dockerfile, - tag=docker_tag, - registry_endpoint=registry_endpoint, - registry_username=pushinfo_response.username, - registry_password=pushinfo_response.password, - docker_build_args=args.docker_build_arg, - ) - - terminal.info("[🚀] deploying...", end=" ") - up_response = await application.idempotently().Up( - context, - digest=digest, - size=SIZE_NAME_TO_ENUM[args.size], - ) - - except Aborted as aborted: - if isinstance(aborted.error, InvalidInputError): - terminal.fail("🛑 failed:\n" - f" {aborted.error.reason}") - elif isinstance(aborted.error, ConcurrentModificationError): - terminal.fail( - "🛑 failed:\n" - " The application is already being `up`ped or `down`ed. " - "Please wait until that operation completes." - ) - elif isinstance(aborted.error, PaymentMethodRequiredError): - terminal.fail( - "🛑 failed:\n" - f" Organization '{organization_name}' does not have a " - "valid payment method. Please add a payment method before " - "deploying applications." - ) - elif isinstance(aborted.error, PermissionDenied): - # Invariant for applications before organizations were - # added was that users _always_ had admin permissions for - # their own applications so we should only get - # `PermissionDenied` for applications launched after - # organizations were introduced. - assert organization_name is not None - terminal.fail( - "🛑 failed:\n" - f" User '{user_id}' does not have permission to bring up " - f"applications in the organization '{organization_name}', " - "only owners and editors are allowed." - ) - else: - print(f"🛑 unexpected error: {aborted}") - traceback.print_exc() - terminal.fail("Please report this bug to the maintainers") - - async for status_response in application.reactively().RevisionStatus( - context, revision_number=up_response.revision_number - ): - revision = status_response.revision - if revision.status == Status.UPPING: - # Keep waiting. - continue - - if revision.status == Status.UP: - url = _application_url( - ApplicationId(up_response.application_id), - args.cloud_url, - ) - terminal.info( - f"✅\n" - "\n" - f" '{args.name}' revision {revision.number}" - " is available:\n" - "\n" - f" Your API is available at: {url}\n" - f" MCP clients can connect at: {url}/mcp\n" - " You can inspect your state at: " - f"{url}/__/inspect\n" - ) - return 0 - - if revision.status == Status.FAILED: - terminal.error( - "🛑 failed:\n" - f"Could not deploy revision {revision.number}:\n" - f" {revision.failure_reason}\n" - "\n" - f"### Logs for revision {revision.number} ###" - ) - await _cloud_logs( - user_id=user_id, - organization_name=organization_name, - qualified_application_name=qualified_application_name, - source=SourceType.CONFIG, - name=args.name, - cloud_url=args.cloud_url, - api_key=args.api_key, - revisions=f"{revision.number}", - last=None, # Show all logs for this revision. - show_revision=False, - show_source=False, - show_timestamp=False, - follow=False, - ) - terminal.error( - f"### End of logs for revision {revision.number} ###\n" - "Please correct the issue and try again." - ) - # ISSUE(https://github.com/reboot-dev/mono/issues/4501): don't exit - # with `sys.exit(1)` here; it causes an unexplained stack trace. - # Simply return the error code instead and do a `sys.exit()` in the - # caller, when the `asyncio.run()` is done. - return 1 - - if revision.status == Status.DOWNING or revision.status == Status.DOWN: - terminal.fail( - "🛑 failed:\n" - " The application is being `down`ed. Please wait until that " - "operation completes." - ) - - # A revision that we `Up()`ed will never be in the `DOWNING` or `DOWN` - # state. Those only appear for revisions created by calling `Down()`. - raise ValueError( - f"Application reached an unexpected status: '{revision.status}'. " - "Please report this bug to the maintainers." - ) - - # Should be unreachable, but need for the type checking. - return 1 - - -async def cloud_down(args: argparse.Namespace) -> None: - """Implementation of the 'cloud down' subcommand.""" - - if not args.expunge: - terminal.fail( - "Currently all applications brought down are expunged. " - "Support for bringing down without expunging will be " - "added in a future release, please see: " - "https://github.com/reboot-dev/reboot/issues/71" - ) - - user_id, qualified_application_name, organization_name = ( - await _parse_common_cloud_args(args) - ) - - context = ExternalContext( - name="cloud-down", - url=args.cloud_url, - bearer_token=args.api_key, - ) - - try: - response = await Application.ref( - qualified_application_name, - ).Down(context) - - if response.status == Status.DOWNING: - # TODO(rjh): once the CLoud waits to resolve - # `down_response.down_task_id` until the application has - # terminated, await the completion of - # `down_response.down_task_id` here, and tell the user - # when their application has in fact terminated. - - terminal.info(f"Application '{args.name}' is being terminated...") - elif response.status in (Status.DOWN, Status.EXPUNGED): - terminal.info(f"Application '{args.name}' is already terminated.") - except Aborted as aborted: - match aborted.error: - case StateNotConstructed(): # type: ignore[misc] - if organization_name is None: - terminal.fail( - f"User '{user_id}' does not have an application " - f"named '{args.name}'. If the application " - "belongs to an organization, try adding " - "--organization=." - ) - else: - terminal.fail( - f"Organization '{organization_name}' does not have " - f"an application named '{args.name}'" - ) - case PermissionDenied(): # type: ignore[misc] - # Invariant for applications before organizations were - # added was that users _always_ had admin permissions - # for their own applications so we should only get - # `PermissionDenied` for applications launched after - # organizations were introduced. - assert organization_name is not None - terminal.fail( - f"User '{user_id}' does not have permission to bring down " - f"applications in the organization '{organization_name}'" - ) - case _: - # There are no other expected errors for `Down()`. - traceback.print_exc() - terminal.fail("Please report this bug to the maintainers") - - -async def cloud_logs(args: argparse.Namespace) -> None: - """Implementation of the 'cloud logs' subcommand""" - user_id, qualified_application_name, organization_name = ( - await _parse_common_cloud_args(args) - ) - - source = SourceType[args.source.upper()] - await _cloud_logs( - user_id=user_id, - organization_name=organization_name, - qualified_application_name=qualified_application_name, - source=source, - name=args.name, - cloud_url=args.cloud_url, - api_key=args.api_key, - revisions=args.revisions, - last=args.last, - show_revision=args.show_revision, - show_source=args.show_source, - show_timestamp=args.show_timestamp, - follow=args.follow, - ) - - -async def _cloud_logs( - *, - user_id: UserId, - organization_name: Optional[OrganizationName], - qualified_application_name: QualifiedApplicationName, - source: SourceType, - name: str, - cloud_url: str, - api_key: str, - revisions: str, - last: Optional[int], - show_revision: Optional[bool], - show_source: Optional[bool], - show_timestamp: bool, - follow: bool, -) -> None: - context = ExternalContext( - name="cloud-logs", - url=cloud_url, - bearer_token=api_key, - ) - - application = Application.ref(qualified_application_name) - - revision_filters = parse_revisions(revisions) - show_revision = ( - # Obey explicit user instructions. - show_revision if show_revision is not None - # If there are no explicit instructions, show revision numbers - # if we request logs for more than one revision. - else _may_select_multiple_revisions( - revision_filters=revision_filters, - follow=follow, - ) - ) - show_source = ( - # Obey explicit user instructions. - show_source if show_source is not None - # If there are no explicit instructions, show sources if we request - # logs from multiple source types - else source == SourceType.ALL - ) - - proto_sources = ( - [Source.SERVER] if source == SourceType.SERVER else - ([Source.CONFIG_RUN] if source == SourceType.CONFIG else []) - ) - - # If the user specified `--follow`, we assume that they only want - # the last few lines of old logs before the new logs start. They can - # manually override this by specifying `--last` to get more lines. - if follow and last is None: - last = 20 - - # We'll retry in the event of `Unavailable` after a backoff, but - # not if we've already gotten some logs because then we want to - # exit so the logs don't show up weirdly. - # - # ISSUE(https://github.com/reboot-dev/mono/issues/4599): retry even - # after the first logs have been delivered, e.g. during `--follow`. - started = False - backoff = Backoff() - while not started: - try: - printed_lines = 0 - async for response in application.Logs( - context, - follow=follow, - revisions=revision_filters, - last=last, - sources=proto_sources, - ): - started = True - for record in response.records: - printed_lines += 1 - - extra_info: list[str] = [] - if show_timestamp: - # Print the timestamp in the local timezone. - timestamp = DateTimeWithTimeZone.from_protobuf_timestamp( - record.timestamp - ) - extra_info.append(f"{timestamp.astimezone()}") - if show_source: - # Print the source of the log line. - if record.WhichOneof("source") == "server_id": - # The server ID is in fully-qualified format, - # including the application ID (e.g. - # 'a12345678-c000001'). The application ID isn't - # interesting here; strip it. - server_id = record.server_id.split("-")[-1] - extra_info.append(f"server={server_id}") - elif record.WhichOneof("source") == "config_run_id": - config_run_id = record.config_run_id.removeprefix( - "config-run-" - ) - # Add two spaces of padding after the config run ID - # to make the text the same length as when we show - # a server ID. - extra_info.append(f"config={config_run_id}") - else: - extra_info.append("source=???") - if show_revision: - extra_info.append(f"revision={record.revision_number}") - - line = "" - if extra_info: - line = f"[{', '.join(extra_info)}] " - - line += record.text - print(line) - if source == SourceType.SERVER and printed_lines == 0: - # The user isn't seeing any output, and it's possible - # that we filtered out some log lines. Print a message - # to that effect. - terminal.warn( - "Serving servers had no logs matching the requested " - "filters. Lines from non-serving sources were filtered " - "out; use `--source=all` to see them." - ) - break - - except Aborted as aborted: - match aborted.error: - case StateNotConstructed(): # type: ignore[misc] - if organization_name is None: - terminal.fail( - f"User '{user_id}' does not have an " - f"application named '{name}'. If the " - "application belongs to an organization, " - "try adding --organization=." - ) - else: - terminal.fail( - f"Organization '{organization_name}' does not have an " - f"application named '{name}'" - ) - case Unavailable(): # type: ignore[misc] - if started: - terminal.fail( - "\n... connection interrupted, please try again" - ) - await backoff() - continue - case PermissionDenied(): # type: ignore[misc] - # Invariant for applications before organizations were - # added was that users _always_ had admin permissions - # for their own applications so we should only get - # `PermissionDenied` for applications launched after - # organizations were introduced. - assert organization_name is not None - terminal.fail( - f"User '{user_id}' does not have permission to view the " - f"logs of application '{name}' in the organization " - f"'{organization_name}'; are you a member of the organization?" - ) - case _: - # There are no other expected errors for - # `Logs()`. Most notably, `PermissionDenied` can't - # happen, since the application we're attempting - # to call `Logs()` on is by definition owned by - # the user. - terminal.fail(f"\nUnexpected error: {aborted}") - - -async def _docker_build_and_push( - dockerfile: Path, - tag: str, - registry_endpoint: str, - registry_username: str, - registry_password: str, - docker_build_args: Optional[list[str]] = None, -) -> str: - """ - Builds and pushes an image with the given `tag` from the `dockerfile`. - - Returns the digest of the pushed image. - """ - assert dockerfile.is_absolute() - if not dockerfile.exists() or not dockerfile.is_file(): - terminal.fail(f"🛑 Could not find Dockerfile '{dockerfile}'") - - dockerfile_pretty = str(dockerfile) - try: - dockerfile_pretty = str(dockerfile.relative_to(Path.cwd())) - if not dockerfile_pretty.startswith("."): - dockerfile_pretty = f"./{dockerfile_pretty}" - except ValueError: - # This means the Dockerfile is not in the current working directory. - # That's OK, we'll simply use the absolute path. - pass - - await run_command( - command=[ - "docker", - "buildx", - "build", - # Reboot Cloud runs on AMD64, so its images must be built for that - # platform. - "--platform", - "linux/amd64", - "--file", - str(dockerfile), - "--tag", - tag, - ] + [f"--build-arg={arg}" for arg in docker_build_args or []] + [ - ".", - ], - cwd=str(dockerfile.parent), - icon="🐳", - command_name="build", - explanation=f"building container from '{dockerfile_pretty}'", - capture_output=False, - ) - - try: - # The push step we do with config from a temporary directory, which - # allows us to use a one-time Docker `config.json`. That avoids - # permanently storing the user's credentials in their normal - # `~/.docker/config.json`. - with tempfile.TemporaryDirectory() as tempdir: - # Create a temporary Docker configuration file. - docker_config = Path(tempdir) / "config.json" - docker_config.write_text( - json.dumps( - { - "auths": - { - registry_endpoint: - { - "auth": - base64.b64encode( - f"{registry_username}:{registry_password}" - .encode() - ).decode(), - } - } - } - ) - ) - - # Push the image with the temporary Docker configuration. - await run_command( - command=[ - "docker", - "--config", - str(tempdir), - "push", - tag, - ], - icon="🚛", - command_name="push", - explanation="pushing container", - capture_output=False, - ) - - # Obtain the image digest. - digest = ( - await run_command( - command=[ - "docker", - "inspect", - "--format", - "{{index .RepoDigests 0}}", - tag, - ], - icon="👀", - command_name="inspect", - explanation="inspecting container", - capture_output=True, - only_show_if_verbose=True, - ) - ).rsplit("@", 1)[-1] - - return digest - - finally: - # Remove the tag from the local Docker daemon so that it doesn't pollute the - # user's list of images. - await run_command( - command=[ - "docker", - "image", - "remove", - tag, - ], - icon="🧹", - command_name="cleanup", - explanation="cleaning up", - capture_output=False, - only_show_if_verbose=True, - ) diff --git a/reboot/cli/cloud/BUILD.bazel b/reboot/cli/cloud/BUILD.bazel new file mode 100644 index 00000000..5f71bcdc --- /dev/null +++ b/reboot/cli/cloud/BUILD.bazel @@ -0,0 +1,103 @@ +load("@rules_python//python:defs.bzl", "py_library") + +py_library( + name = "__init___py", + srcs = ["__init__.py"], + srcs_version = "PY3", + visibility = ["//visibility:public"], + deps = [ + ":down_py", + ":logs_py", + ":secrets_py", + ":up_py", + "//reboot/cli:rc_py", + ], +) + +py_library( + name = "common_py", + srcs = ["common.py"], + srcs_version = "PY3", + visibility = ["//visibility:public"], + deps = [ + "//reboot:api_keys_py", + "//reboot/cli:rc_py", + "//reboot/cli:terminal_py", + "@com_github_reboot_dev_reboot//rbt/cloud/v1alpha1/auth:auth_py_reboot", + "@com_github_reboot_dev_reboot//rbt/cloud/v1alpha1/organizations:organizations_py_reboot", + "@com_github_reboot_dev_reboot//rbt/v1alpha1:errors_py_proto", + "@com_github_reboot_dev_reboot//reboot:naming_py", + "@com_github_reboot_dev_reboot//reboot/aio:aborted_py", + "@com_github_reboot_dev_reboot//reboot/aio:external_py", + ], +) + +py_library( + name = "up_py", + srcs = ["up.py"], + srcs_version = "PY3", + visibility = ["//visibility:public"], + deps = [ + ":common_py", + ":logs_py", + "//reboot/cli:commands_py", + "//reboot/cli:rc_py", + "//reboot/cli:terminal_py", + "@com_github_reboot_dev_reboot//rbt/cloud/v1alpha1/application:application_py_reboot", + "@com_github_reboot_dev_reboot//rbt/v1alpha1:errors_py_proto", + "@com_github_reboot_dev_reboot//reboot:naming_py", + "@com_github_reboot_dev_reboot//reboot/aio:aborted_py", + "@com_github_reboot_dev_reboot//reboot/aio:external_py", + ], +) + +py_library( + name = "down_py", + srcs = ["down.py"], + srcs_version = "PY3", + visibility = ["//visibility:public"], + deps = [ + ":common_py", + "//reboot/cli:rc_py", + "//reboot/cli:terminal_py", + "@com_github_reboot_dev_reboot//rbt/cloud/v1alpha1/application:application_py_reboot", + "@com_github_reboot_dev_reboot//rbt/v1alpha1:errors_py_proto", + "@com_github_reboot_dev_reboot//reboot/aio:aborted_py", + "@com_github_reboot_dev_reboot//reboot/aio:external_py", + ], +) + +py_library( + name = "logs_py", + srcs = ["logs.py"], + srcs_version = "PY3", + visibility = ["//visibility:public"], + deps = [ + ":common_py", + "//reboot/cli:rc_py", + "//reboot/cli:terminal_py", + "@com_github_reboot_dev_reboot//rbt/cloud/v1alpha1/application:application_py_reboot", + "@com_github_reboot_dev_reboot//rbt/cloud/v1alpha1/logs:logs_py_reboot", + "@com_github_reboot_dev_reboot//rbt/v1alpha1:errors_py_proto", + "@com_github_reboot_dev_reboot//reboot:naming_py", + "@com_github_reboot_dev_reboot//reboot:time_py", + "@com_github_reboot_dev_reboot//reboot/aio:aborted_py", + "@com_github_reboot_dev_reboot//reboot/aio:external_py", + "@com_github_reboot_dev_reboot//reboot/aio/backoff:python", + ], +) + +py_library( + name = "secrets_py", + srcs = ["secrets.py"], + srcs_version = "PY3", + visibility = ["//visibility:public"], + deps = [ + ":common_py", + "//reboot/cli:rc_py", + "//reboot/cli:terminal_py", + "@com_github_reboot_dev_reboot//rbt/cloud/v1alpha1/application:application_py_reboot", + "@com_github_reboot_dev_reboot//reboot/aio:aborted_py", + "@com_github_reboot_dev_reboot//reboot/aio:external_py", + ], +) diff --git a/reboot/cli/cloud/__init__.py b/reboot/cli/cloud/__init__.py new file mode 100644 index 00000000..674f1384 --- /dev/null +++ b/reboot/cli/cloud/__init__.py @@ -0,0 +1,53 @@ +import argparse +from reboot.cli.cloud.down import cloud_down, register_cloud_down +from reboot.cli.cloud.logs import cloud_logs, register_cloud_logs +from reboot.cli.cloud.secrets import ( + cloud_secret_delete, + cloud_secret_list, + cloud_secret_set, + register_cloud_secret, +) +from reboot.cli.cloud.up import cloud_up, register_cloud_up +from reboot.cli.rc import ArgumentParser +from typing import Optional + + +def cloud_subcommands() -> list[str]: + return [ + 'cloud down', + 'cloud up', + 'cloud secret set', + 'cloud secret list', + 'cloud secret delete', + 'cloud logs', + ] + + +def register_cloud(parser: ArgumentParser) -> None: + register_cloud_secret(parser) + register_cloud_down(parser) + register_cloud_logs(parser) + register_cloud_up(parser) + + +async def handle_cloud_subcommand( + args: argparse.Namespace, +) -> Optional[int]: + if args.subcommand == 'cloud secret set': + await cloud_secret_set(args) + return 0 + elif args.subcommand == 'cloud secret list': + await cloud_secret_list(args) + return 0 + elif args.subcommand == 'cloud secret delete': + await cloud_secret_delete(args) + return 0 + elif args.subcommand == 'cloud up': + return await cloud_up(args) + elif args.subcommand == 'cloud down': + await cloud_down(args) + return 0 + elif args.subcommand == 'cloud logs': + await cloud_logs(args) + return 0 + return None diff --git a/reboot/cli/cloud/common.py b/reboot/cli/cloud/common.py new file mode 100644 index 00000000..b29c32c3 --- /dev/null +++ b/reboot/cli/cloud/common.py @@ -0,0 +1,199 @@ +import argparse +import traceback +from rbt.cloud.v1alpha1.auth.auth_rbt import APIKey +from rbt.cloud.v1alpha1.organizations.organizations_rbt import Organizations +from rbt.v1alpha1.errors_pb2 import ( + PermissionDenied, + StateNotConstructed, + Unauthenticated, +) +from reboot.aio.aborted import Aborted +from reboot.aio.external import ExternalContext +from reboot.aio.types import ApplicationId +from reboot.api_keys import ( + InvalidAPIKeyBearerToken, + parse_api_key_bearer_token, +) +from reboot.cli import terminal +from reboot.cli.rc import SubcommandParser +from reboot.naming import ( + ORGANIZATIONS_ID, + OrganizationId, + OrganizationName, + QualifiedApplicationName, + UserId, + make_qualified_application_name_from_owner_id_and_application_name, +) +from typing import Optional + +DEFAULT_REBOOT_CLOUD_URL = "https://cloud.prod1.rbt.cloud:9991" + +_API_KEY_FLAG = '--api-key' + + +def _add_common_cloud_args(subcommand: SubcommandParser) -> None: + """Adds flags common to every `rbt cloud` subcommand.""" + # TODO: Consider moving these to flags on the `cloud` subcommand using #3845 + + subcommand.add_argument( + '--cloud-url', + type=str, + help="the URL of the Reboot cloud API", + default=DEFAULT_REBOOT_CLOUD_URL, + non_empty_string=True, + ) + # TODO: This should probably be read from a file by default. + subcommand.add_argument( + _API_KEY_FLAG, + type=str, + help="the API key to use to connect to the Reboot Cloud API", + default=None, + required=True, + non_empty_string=True, + ) + subcommand.add_argument( + '--organization', + type=str, + help="the organization to use for the application", + default=None, + non_empty_string=True, + ) + + subcommand.add_argument( + '--application-name', + type=str, + required=True, + help="name of the application", + non_empty_string=True, + ) + subcommand.add_renamed_flag('--name', '--application-name') + + +def _application_url(application_id: ApplicationId, cloud_url: str) -> str: + """ + Given a cloud URL (e.g. `https://cloud.prod1.rbt.cloud:9991`), returns the + url for the given application (e.g. `https://a12345.prod1.rbt.cloud:9991`). + """ + if not ( + cloud_url.startswith("https://") or cloud_url.startswith("http://") + ): + terminal.fail( + f"Cloud URL '{cloud_url}' must have 'https://' or 'http://'." + ) + protocol, hostname_port = cloud_url.split("://", maxsplit=1) + if not hostname_port.startswith("cloud."): + terminal.fail( + f"Cloud host '{hostname_port}' is missing expected 'cloud.' prefix" + ) + cell_hostname_port = hostname_port.removeprefix("cloud.") + return f"{protocol}://{application_id}.{cell_hostname_port}" + + +async def _user_id_from_api_key(api_key: str, cloud_url: str) -> str: + try: + api_key_id, api_key_secret = parse_api_key_bearer_token(token=api_key) + except InvalidAPIKeyBearerToken: + # Note that we do not log the API key contents; they are a secret, which + # we don't want to output to a log file (if any). + terminal.fail( + "Invalid API key shape (expected: " + "'XXXXXXXXXX-XXXXXXXXXXXXXXXXXXXX')" + ) + + context = ExternalContext( + name="user-id-from-api-key", + url=cloud_url, + # TODO(rjh): once APIKey reads the bearer token for `Authenticate`, use + # that instead of passing `secret` in the proto below. + ) + + try: + return ( + await APIKey.ref(api_key_id).Authenticate( + context, + secret=api_key_secret, + ) + ).user_id + except Aborted as aborted: + match aborted.error: + case StateNotConstructed( # type: ignore[misc] + ) | PermissionDenied( # type: ignore[misc] + ) | Unauthenticated(): # type: ignore[misc] + # Note that we do not log the API key contents; they + # are a secret, which we don't want to output to a log + # file (if any). + terminal.fail("Invalid API key") + case _: + terminal.fail(f"Unexpected error: {aborted}") + + +async def _get_organization_id( + *, + user_id: UserId, + organization_name: OrganizationName, + cloud_url: str, + api_key: str, +) -> Optional[OrganizationId]: + # Validate the organization name is nonempty. + if organization_name.strip() == "": + terminal.fail("Invalid organization, got empty string") + + context = ExternalContext( + name="get-organization-id", + url=cloud_url, + bearer_token=api_key, + ) + + organizations = Organizations.ref(ORGANIZATIONS_ID) + try: + response = await organizations.resolve( + context, + organization_name=organization_name, + ) + except Aborted as aborted: + match aborted.error: + case _: + # Not expecting any errors; invariant here is that + # we've already authenticated the API key because we + # have a user ID (which we can only get from + # authenticating the API key). + traceback.print_exc() + terminal.fail("Please report this bug to the maintainers") + else: + if response.HasField("organization_id"): + return response.organization_id + return None + + +async def _parse_common_cloud_args( + args: argparse.Namespace +) -> tuple[UserId, QualifiedApplicationName, Optional[OrganizationName]]: + user_id = await _user_id_from_api_key( + api_key=args.api_key, + cloud_url=args.cloud_url, + ) + + # If --organization was specified (which is not required for + # backwards compatibility) then get its ID. This also validates + # that the organization exists which provides a better experience + # for our users that might have just misspelled it. + organization_name: Optional[OrganizationName] = args.organization + organization_id: Optional[OrganizationId] = None + if organization_name is not None: + organization_id = await _get_organization_id( + user_id=user_id, + organization_name=organization_name, + cloud_url=args.cloud_url, + api_key=args.api_key, + ) + if organization_id is None: + terminal.fail(f"Organization '{organization_name}' does not exist") + + qualified_application_name = ( + make_qualified_application_name_from_owner_id_and_application_name( + owner_id=organization_id or user_id, + application_name=args.application_name, + ) + ) + + return user_id, qualified_application_name, organization_name diff --git a/reboot/cli/cloud/down.py b/reboot/cli/cloud/down.py new file mode 100644 index 00000000..fc286257 --- /dev/null +++ b/reboot/cli/cloud/down.py @@ -0,0 +1,97 @@ +import argparse +import traceback +from rbt.cloud.v1alpha1.application.application_pb2 import Status +from rbt.cloud.v1alpha1.application.application_rbt import Application +from rbt.v1alpha1.errors_pb2 import PermissionDenied, StateNotConstructed +from reboot.aio.aborted import Aborted +from reboot.aio.external import ExternalContext +from reboot.cli import terminal +from reboot.cli.cloud.common import ( + _add_common_cloud_args, + _parse_common_cloud_args, +) +from reboot.cli.rc import ArgumentParser + + +def register_cloud_down(parser: ArgumentParser) -> None: + down_subcommand = parser.subcommand('cloud down') + _add_common_cloud_args(down_subcommand) + down_subcommand.add_argument( + '--expunge', + type=bool, + required=True, + help='if true, expunge all application state when bringing ' + 'the application down', + ) + + +async def cloud_down(args: argparse.Namespace) -> None: + """Implementation of the 'cloud down' subcommand.""" + + if not args.expunge: + terminal.fail( + "Currently all applications brought down are expunged. " + "Support for bringing down without expunging will be " + "added in a future release, please see: " + "https://github.com/reboot-dev/reboot/issues/71" + ) + + user_id, qualified_application_name, organization_name = ( + await _parse_common_cloud_args(args) + ) + + context = ExternalContext( + name="cloud-down", + url=args.cloud_url, + bearer_token=args.api_key, + ) + + try: + response = await Application.ref( + qualified_application_name, + ).Down(context) + + if response.status == Status.DOWNING: + # TODO(rjh): once the CLoud waits to resolve + # `down_response.down_task_id` until the application has + # terminated, await the completion of + # `down_response.down_task_id` here, and tell the user + # when their application has in fact terminated. + + terminal.info( + f"Application '{args.application_name}' is being terminated..." + ) + elif response.status in (Status.DOWN, Status.EXPUNGED): + terminal.info( + f"Application '{args.application_name}' is already terminated." + ) + except Aborted as aborted: + match aborted.error: + case StateNotConstructed(): # type: ignore[misc] + if organization_name is None: + terminal.fail( + f"User '{user_id}' does not have an application " + f"named '{args.application_name}'. If the application " + "belongs to an organization, try adding " + "--organization=." + ) + else: + terminal.fail( + f"Organization '{organization_name}' does not have " + f"an application named '{args.application_name}'" + ) + case PermissionDenied(): # type: ignore[misc] + # Invariant for applications before organizations were + # added was that users _always_ had admin permissions + # for their own applications so we should only get + # `PermissionDenied` for applications launched after + # organizations were introduced. + assert organization_name is not None + terminal.fail( + f"User '{user_id}' does not have permission to bring down " + f"applications in the organization '{organization_name}'" + ) + case _: + # There are no other expected errors for `Down()`. + traceback.print_exc() + terminal.fail("Please report this bug to the maintainers") diff --git a/reboot/cli/cloud/logs.py b/reboot/cli/cloud/logs.py new file mode 100644 index 00000000..54e34fc4 --- /dev/null +++ b/reboot/cli/cloud/logs.py @@ -0,0 +1,370 @@ +import argparse +from enum import Enum +from rbt.cloud.v1alpha1.application.application_rbt import Application +from rbt.cloud.v1alpha1.logs.logs_pb2 import LogsRequest, Source +from rbt.v1alpha1.errors_pb2 import ( + PermissionDenied, + StateNotConstructed, + Unavailable, +) +from reboot.aio.aborted import Aborted +from reboot.aio.backoff import Backoff +from reboot.aio.external import ExternalContext +from reboot.cli import terminal +from reboot.cli.cloud.common import ( + _add_common_cloud_args, + _parse_common_cloud_args, +) +from reboot.cli.rc import ArgumentParser +from reboot.naming import OrganizationName, QualifiedApplicationName, UserId +from reboot.time import DateTimeWithTimeZone +from typing import Optional + + +class SourceType(Enum): + SERVER = 'server' + CONFIG = 'config' + ALL = 'all' + + +def register_cloud_logs(parser: ArgumentParser) -> None: + logs_subcommand = parser.subcommand('cloud logs') + _add_common_cloud_args(logs_subcommand) + + logs_subcommand.add_argument( + '--follow', + type=bool, + default=False, + help='if true, follows the logs as they are produced', + ) + + # Filters. + logs_subcommand.add_argument( + '--revisions', + type=str, + # We default to ">=latest" rather than "latest" because the user + # may also use "--follow", in which case they presumably want to + # see all logs from the latest revision _and_ any that are upped + # in the future. If they really don't want to follow future + # revisions they can specify just "latest" manually. + default=">=latest", + help='the revision number(s) to get logs for. ' + 'May specify a single number (e.g. "4"), several comma-separated ' + 'numbers (e.g. "4,7"), or a from-range using ">=" (e.g. ">=4" for logs ' + 'at and beyond revision 4). ' + 'May use "latest" to replace a number. ' + 'Default: ">=latest".', + ) + logs_subcommand.add_argument( + '--source', + type=str, + choices=[value.name.lower() for value in SourceType], + # We default to `server` because, under normal circumstances, + # developers don't care about the logs of their config runs. If a config + # run fails, its `rbt cloud up` will already print those logs. If the + # config run succeeds, its logs are mostly irrelevant. + default=SourceType.SERVER.name.lower(), + help="which sources to show logs from; defaults to " + f"'{SourceType.SERVER.name.lower()}'", + ) + logs_subcommand.add_argument( + '--last', + type=int, + default=None, + help='if set, only show the last N log lines. If not set, show all ' + 'log lines.', + ) + + # Presentation. + logs_subcommand.add_argument( + '--show-revision', + type=bool, + default=None, + help='if true, show the revision number of the log line in the logs. ' + 'If false, never do so. If unset, will show revision numbers if there ' + 'may be more than one revision in the logs.', + ) + logs_subcommand.add_argument( + '--show-source', + type=bool, + default=None, + help='if true, show information about the source of the log ' + 'line in the logs. If false, never do so. If unset, will show source ' + 'information if there may be more than one type of source in the logs.', + ) + logs_subcommand.add_argument( + '--show-timestamp', + type=bool, + default=False, + help='if true, includes timestamp in the logs', + ) + + +def parse_revisions( + revisions: str, +) -> list[LogsRequest.RevisionFilter]: + """ + Parse a revisions string (e.g. "0, >=4") + + Parses these into `LogsRequest.RevisionFilter` protos, which can be + sent to the Reboot Cloud. + """ + original_revisions = revisions + + filters: list[LogsRequest.RevisionFilter] = [] + + for revision in revisions.split(","): + revision = revision.strip() + operator: LogsRequest.RevisionFilter.Operator.ValueType = LogsRequest.RevisionFilter.Operator.EQUAL + right_hand_revision_number: Optional[int] = None + right_hand_latest: Optional[bool] = None + + if revision.startswith(">="): + revision = revision.removeprefix(">=") + operator = LogsRequest.RevisionFilter.Operator.GREATER_THAN_OR_EQUAL + elif revision.startswith("="): + # Equal is the default operator; simply remove the prefix. + # Support both `=` and `==` as prefixes. + revision = revision.removeprefix("=") + revision = revision.removeprefix("=") + + if revision == "latest": + right_hand_latest = True + else: + try: + right_hand_revision_number = int(revision) + except ValueError: + terminal.fail( + f"Invalid revision number '{revision}' in " + f"--revision='{original_revisions}'; expected a " + "number or 'latest'." + ) + + filter = LogsRequest.RevisionFilter() + filter.operator = operator + if right_hand_latest is not None: + filter.right_hand_latest = right_hand_latest + else: + assert right_hand_revision_number is not None + filter.right_hand_revision_number = right_hand_revision_number + + filters.append(filter) + + return filters + + +def _may_select_multiple_revisions( + revision_filters: list[LogsRequest.RevisionFilter], + follow: bool, +) -> bool: + """ + Returns True if the `revisions` may pass logs for multiple revisions. + """ + assert len(revision_filters) > 0, "Must have at least one revision filter" + if len(revision_filters) > 1: + # TODO: it's possible for a user to write e.g. "0,0", and + # ideally we'd understand that that's a single revision, + # but it's not a big deal. + return True + + # Special case: ">=latest" is a single revision, EXCEPT if we're + # following. + revision_filter = revision_filters[0] + if ( + revision_filter.operator + == LogsRequest.RevisionFilter.Operator.GREATER_THAN_OR_EQUAL and + revision_filter.right_hand_latest + ): + return follow + + # Otherwise, it's down to the operator. + return revision_filter.operator != LogsRequest.RevisionFilter.Operator.EQUAL + + +async def cloud_logs(args: argparse.Namespace) -> None: + """Implementation of the 'cloud logs' subcommand""" + user_id, qualified_application_name, organization_name = ( + await _parse_common_cloud_args(args) + ) + + source = SourceType[args.source.upper()] + await _cloud_logs( + user_id=user_id, + organization_name=organization_name, + qualified_application_name=qualified_application_name, + source=source, + name=args.application_name, + cloud_url=args.cloud_url, + api_key=args.api_key, + revisions=args.revisions, + last=args.last, + show_revision=args.show_revision, + show_source=args.show_source, + show_timestamp=args.show_timestamp, + follow=args.follow, + ) + + +async def _cloud_logs( + *, + user_id: UserId, + organization_name: Optional[OrganizationName], + qualified_application_name: QualifiedApplicationName, + source: SourceType, + name: str, + cloud_url: str, + api_key: str, + revisions: str, + last: Optional[int], + show_revision: Optional[bool], + show_source: Optional[bool], + show_timestamp: bool, + follow: bool, +) -> None: + context = ExternalContext( + name="cloud-logs", + url=cloud_url, + bearer_token=api_key, + ) + + application = Application.ref(qualified_application_name) + + revision_filters = parse_revisions(revisions) + show_revision = ( + # Obey explicit user instructions. + show_revision if show_revision is not None + # If there are no explicit instructions, show revision numbers + # if we request logs for more than one revision. + else _may_select_multiple_revisions( + revision_filters=revision_filters, + follow=follow, + ) + ) + show_source = ( + # Obey explicit user instructions. + show_source if show_source is not None + # If there are no explicit instructions, show sources if we request + # logs from multiple source types + else source == SourceType.ALL + ) + + proto_sources = ( + [Source.SERVER] if source == SourceType.SERVER else + ([Source.CONFIG_RUN] if source == SourceType.CONFIG else []) + ) + + # If the user specified `--follow`, we assume that they only want + # the last few lines of old logs before the new logs start. They can + # manually override this by specifying `--last` to get more lines. + if follow and last is None: + last = 20 + + # We'll retry in the event of `Unavailable` after a backoff, but + # not if we've already gotten some logs because then we want to + # exit so the logs don't show up weirdly. + # + # ISSUE(https://github.com/reboot-dev/mono/issues/4599): retry even + # after the first logs have been delivered, e.g. during `--follow`. + started = False + backoff = Backoff() + while not started: + try: + printed_lines = 0 + async for response in application.Logs( + context, + follow=follow, + revisions=revision_filters, + last=last, + sources=proto_sources, + ): + started = True + for record in response.records: + printed_lines += 1 + + extra_info: list[str] = [] + if show_timestamp: + # Print the timestamp in the local timezone. + timestamp = DateTimeWithTimeZone.from_protobuf_timestamp( + record.timestamp + ) + extra_info.append(f"{timestamp.astimezone()}") + if show_source: + # Print the source of the log line. + if record.WhichOneof("source") == "server_id": + # The server ID is in fully-qualified format, + # including the application ID (e.g. + # 'a12345678-c000001'). The application ID isn't + # interesting here; strip it. + server_id = record.server_id.split("-")[-1] + extra_info.append(f"server={server_id}") + elif record.WhichOneof("source") == "config_run_id": + config_run_id = record.config_run_id.removeprefix( + "config-run-" + ) + # Add two spaces of padding after the config run ID + # to make the text the same length as when we show + # a server ID. + extra_info.append(f"config={config_run_id}") + else: + extra_info.append("source=???") + if show_revision: + extra_info.append(f"revision={record.revision_number}") + + line = "" + if extra_info: + line = f"[{', '.join(extra_info)}] " + + line += record.text + print(line) + if source == SourceType.SERVER and printed_lines == 0: + # The user isn't seeing any output, and it's possible + # that we filtered out some log lines. Print a message + # to that effect. + terminal.warn( + "Serving servers had no logs matching the requested " + "filters. Lines from non-serving sources were filtered " + "out; use `--source=all` to see them." + ) + break + + except Aborted as aborted: + match aborted.error: + case StateNotConstructed(): # type: ignore[misc] + if organization_name is None: + terminal.fail( + f"User '{user_id}' does not have an " + f"application named '{name}'. If the " + "application belongs to an organization, " + "try adding --organization=." + ) + else: + terminal.fail( + f"Organization '{organization_name}' does not have an " + f"application named '{name}'" + ) + case Unavailable(): # type: ignore[misc] + if started: + terminal.fail( + "\n... connection interrupted, please try again" + ) + await backoff() + continue + case PermissionDenied(): # type: ignore[misc] + # Invariant for applications before organizations were + # added was that users _always_ had admin permissions + # for their own applications so we should only get + # `PermissionDenied` for applications launched after + # organizations were introduced. + assert organization_name is not None + terminal.fail( + f"User '{user_id}' does not have permission to view the " + f"logs of application '{name}' in the organization " + f"'{organization_name}'; are you a member of the organization?" + ) + case _: + # There are no other expected errors for + # `Logs()`. Most notably, `PermissionDenied` can't + # happen, since the application we're attempting + # to call `Logs()` on is by definition owned by + # the user. + terminal.fail(f"\nUnexpected error: {aborted}") diff --git a/reboot/cli/cloud/secrets.py b/reboot/cli/cloud/secrets.py new file mode 100644 index 00000000..90527734 --- /dev/null +++ b/reboot/cli/cloud/secrets.py @@ -0,0 +1,202 @@ +import argparse +import os +import traceback +from rbt.cloud.v1alpha1.application.application_pb2 import InvalidInputError +from rbt.cloud.v1alpha1.application.application_rbt import Application +from rbt.v1alpha1.errors_pb2 import PermissionDenied, StateNotConstructed +from reboot.aio.aborted import Aborted +from reboot.aio.external import ExternalContext +from reboot.cli import terminal +from reboot.cli.cloud.common import ( + _add_common_cloud_args, + _parse_common_cloud_args, +) +from reboot.cli.rc import ArgumentParser + + +def register_cloud_secret(parser: ArgumentParser) -> None: + secret_set = parser.subcommand('cloud secret set') + _add_common_cloud_args(secret_set) + secret_set.add_argument( + 'key_values', + type=str, + repeatable=True, + required=True, + help=( + "KEY=VALUE pairs, or just KEY to read the value from your " + "environment; keys must be uppercase environment variable " + "names (letters, digits, underscores; not starting with a " + "digit). 'REBOOT_*' and 'RBT_*' are reserved by the Reboot " + "platform" + ), + ) + + secret_list = parser.subcommand('cloud secret list') + _add_common_cloud_args(secret_list) + + secret_delete = parser.subcommand('cloud secret delete') + _add_common_cloud_args(secret_delete) + secret_delete.add_argument( + 'names', + type=str, + repeatable=True, + required=True, + help="names of the secrets to delete", + ) + + +async def cloud_secret_set(args: argparse.Namespace) -> None: + """Implementation of the 'cloud secret set' subcommand.""" + user_id, qualified_application_name, organization_name = ( + await _parse_common_cloud_args(args) + ) + secrets = {} + for key_value in args.key_values: + if '=' not in key_value: + # No value on the command line; read from the environment so + # that secret values don't appear in shell history or + # process listings. + key = key_value + value = os.environ.get(key) + if value is None: + terminal.fail( + f"'{key}' environment variable not found: please set " + f"this environment variable or pass '{key}=[VALUE] if you " + "are okay with the secret value appearing in your shell " + "history and process listings" + ) + else: + key, value = key_value.split('=', 1) + secrets[key] = value + + context = ExternalContext( + name="cloud-secret-set", + url=args.cloud_url, + bearer_token=args.api_key, + ) + application = Application.ref(qualified_application_name) + try: + await application.set_secrets(context, secrets=secrets) + except Aborted as aborted: + match aborted.error: + case StateNotConstructed(): + if organization_name is None: + terminal.fail( + f"User '{user_id}' does not have an application named " + f"'{args.application_name}'. If the application " + "belongs to an organization, try adding " + "--organization=." + ) + else: + terminal.fail( + f"Organization '{organization_name}' does not have an " + f"application named '{args.application_name}'" + ) + case PermissionDenied(): + # Before organizations were added, users always + # had admin permissions for their own apps, so + # `PermissionDenied` only occurs for org apps. + assert organization_name is not None + terminal.fail( + f"User '{user_id}' does not have permission to set secrets " + "for applications in the organization " + f"'{organization_name}', only owners and editors are " + "allowed to do so" + ) + case InvalidInputError(): + terminal.fail(aborted.error.reason) + case _: + traceback.print_exc() + terminal.fail("Please report this bug to the maintainers") + + +async def cloud_secret_list(args: argparse.Namespace) -> None: + """Implementation of the 'cloud secret list' subcommand.""" + user_id, qualified_application_name, organization_name = ( + await _parse_common_cloud_args(args) + ) + context = ExternalContext( + name="cloud-secret-list", + url=args.cloud_url, + bearer_token=args.api_key, + ) + application = Application.ref(qualified_application_name) + try: + response = await application.list_secrets(context) + except Aborted as aborted: + match aborted.error: + case StateNotConstructed(): + if organization_name is None: + terminal.fail( + f"User '{user_id}' does not have an application named " + f"'{args.application_name}'. If the application " + "belongs to an organization, try adding " + "'--organization='." + ) + else: + terminal.fail( + f"Organization '{organization_name}' does not have an " + f"application named '{args.application_name}'" + ) + case PermissionDenied(): + # Before organizations were added, users always had + # admin permissions for their own apps, so + # `PermissionDenied` only occurs for org apps. + assert organization_name is not None + terminal.fail( + f"User '{user_id}' does not have permission to list " + f"secrets for application '{args.application_name}' in the " + f"organization '{organization_name}'; are you a member of " + "the organization?" + ) + case _: + traceback.print_exc() + terminal.fail("Please report this bug to the maintainers") + for name in response.names: + print(name) + + +async def cloud_secret_delete(args: argparse.Namespace) -> None: + """Implementation of the 'cloud secret delete' subcommand.""" + user_id, qualified_application_name, organization_name = ( + await _parse_common_cloud_args(args) + ) + context = ExternalContext( + name="cloud-secret-delete", + url=args.cloud_url, + bearer_token=args.api_key, + ) + application = Application.ref(qualified_application_name) + try: + await application.delete_secrets(context, names=args.names) + except Aborted as aborted: + match aborted.error: + case StateNotConstructed(): + if organization_name is None: + terminal.fail( + f"User '{user_id}' does not have an application named " + f"'{args.application_name}'. If the application " + "belongs to an organization, try adding " + "'--organization='." + ) + else: + terminal.fail( + f"Organization '{organization_name}' does not have an " + f"application named '{args.application_name}'" + ) + case PermissionDenied(): + # Before organizations were added, users always had + # admin permissions for their own apps, so + # `PermissionDenied` only occurs for org apps. + assert organization_name is not None + terminal.fail( + f"User '{user_id}' does not have permission to delete " + "secrets for applications in the organization " + f"'{organization_name}', only owners and editors are " + "allowed" + ) + case InvalidInputError(): + terminal.fail(aborted.error.reason) + case _: + traceback.print_exc() + terminal.fail("Please report this bug to the maintainers") diff --git a/reboot/cli/cloud/up.py b/reboot/cli/cloud/up.py new file mode 100644 index 00000000..54ec44fa --- /dev/null +++ b/reboot/cli/cloud/up.py @@ -0,0 +1,428 @@ +import argparse +import base64 +import json +import tempfile +import traceback +from pathlib import Path +from rbt.cloud.v1alpha1.application.application_pb2 import ( + ApplicationSize, + ConcurrentModificationError, + InvalidInputError, + PaymentMethodRequiredError, + Status, +) +from rbt.cloud.v1alpha1.application.application_rbt import Application +from rbt.v1alpha1.errors_pb2 import ( + PermissionDenied, + StateAlreadyConstructed, + StateNotConstructed, +) +from reboot.aio.aborted import Aborted +from reboot.aio.external import ExternalContext +from reboot.aio.types import ApplicationId +from reboot.cli import terminal +from reboot.cli.cloud.common import ( + _add_common_cloud_args, + _application_url, + _parse_common_cloud_args, +) +from reboot.cli.cloud.logs import SourceType, _cloud_logs +from reboot.cli.commands import run_command +from reboot.cli.rc import ArgumentParser +from reboot.naming import QualifiedApplicationName +from typing import Optional + +SIZE_NAME_TO_ENUM = { + 'xsmall': ApplicationSize.XSMALL, + 'small': ApplicationSize.SMALL, + 'medium': ApplicationSize.MEDIUM, + 'large': ApplicationSize.LARGE, + 'xlarge': ApplicationSize.XLARGE, +} + +VALID_SIZES = list(SIZE_NAME_TO_ENUM.keys()) + + +def register_cloud_up(parser: ArgumentParser) -> None: + up_subcommand = parser.subcommand('cloud up') + _add_common_cloud_args(up_subcommand) + up_subcommand.add_argument( + '--dockerfile', + type=Path, + help='the Dockerfile to build this application from', + default='./Dockerfile', + ) + up_subcommand.add_argument( + '--docker-build-arg', + type=str, + repeatable=True, + help='additional build arguments to pass to the Docker build command. ' + 'Can be specified multiple times, e.g. ' + '`--docker-build-arg=key1=value1 --docker-build-arg=key2`', + ) + up_subcommand.add_argument( + '--size', + type=str, + choices=VALID_SIZES, + default='xsmall', + help='the size of the application', + ) + + +async def _maybe_create_application( + qualified_application_name: QualifiedApplicationName, + cloud_url: str, + api_key: str, +) -> None: + """ + Creates the Application with the given `qualified_application_name` if it + doesn't exist yet. + """ + # Use a separate context for `Create()`, since that call is allowed to fail + # and will then leave its context unable to continue due to idempotency + # uncertainty. + context = ExternalContext( + name="cloud-up-create-application", + url=cloud_url, + bearer_token=api_key, + ) + try: + await Application.Create(context, qualified_application_name) + except Aborted as aborted: + match aborted.error: + case StateAlreadyConstructed(): # type: ignore[misc] + # That's OK; we just want the application to exist! + pass + case _: + # Unexpected error, propagate it. + raise + + +async def _does_application_exist( + *, + qualified_application_name: QualifiedApplicationName, + cloud_url: str, + api_key: str, +) -> bool: + """ + Checks if the `Application` with the given + `qualified_application_name` exists. + """ + # Use a separate context since that call is allowed to fail and + # will then leave its context unable to continue due to + # idempotency uncertainty. + context = ExternalContext( + name="cloud-application-status", + url=cloud_url, + bearer_token=api_key, + ) + try: + await Application.ref(qualified_application_name).status(context) + except Aborted as aborted: + match aborted.error: + case StateNotConstructed(): # type: ignore[misc] + return False + case _: + # Unexpected error, propagate it. + raise + return True + + +async def cloud_up(args: argparse.Namespace) -> int: + """Implementation of the 'cloud up' subcommand.""" + + user_id, qualified_application_name, organization_name = ( + await _parse_common_cloud_args(args) + ) + + # If the application does not yet exist then --org is required! + if organization_name is None and not await _does_application_exist( + qualified_application_name=qualified_application_name, + cloud_url=args.cloud_url, + api_key=args.api_key, + ): + terminal.fail("--organization=... is required for new applications") + + context = ExternalContext( + name="cloud-up", + url=args.cloud_url, + bearer_token=args.api_key, + ) + + try: + terminal.info("[😇] checking permissions...", end=" ") + await _maybe_create_application( + qualified_application_name=qualified_application_name, + cloud_url=args.cloud_url, + api_key=args.api_key, + ) + application = Application.ref(qualified_application_name) + + pushinfo_response = await application.PushInfo(context) + terminal.info("✅") + + registry_endpoint = ( + pushinfo_response.registry_url + # Regardless of whether the prefix is "https" or "http", we must + # remove it; the Docker client will decide for itself whether it + # believes the registry is "secure" or "insecure". We hope it + # guesses right, otherwise the requests will fail. + .removeprefix("https://").removeprefix("http://") + ) + docker_tag = f"{registry_endpoint}/{pushinfo_response.repository}:{pushinfo_response.tag}" + + digest = await _docker_build_and_push( + dockerfile=args.dockerfile, + tag=docker_tag, + registry_endpoint=registry_endpoint, + registry_username=pushinfo_response.username, + registry_password=pushinfo_response.password, + docker_build_args=args.docker_build_arg, + ) + + terminal.info("[🚀] deploying...", end=" ") + up_response = await application.idempotently().Up( + context, + digest=digest, + size=SIZE_NAME_TO_ENUM[args.size], + ) + + except Aborted as aborted: + if isinstance(aborted.error, InvalidInputError): + terminal.fail("🛑 failed:\n" + f" {aborted.error.reason}") + elif isinstance(aborted.error, ConcurrentModificationError): + terminal.fail( + "🛑 failed:\n" + " The application is already being `up`ped or `down`ed. " + "Please wait until that operation completes." + ) + elif isinstance(aborted.error, PaymentMethodRequiredError): + terminal.fail( + "🛑 failed:\n" + f" Organization '{organization_name}' does not have a " + "valid payment method. Please add a payment method before " + "deploying applications." + ) + elif isinstance(aborted.error, PermissionDenied): + # Invariant for applications before organizations were + # added was that users _always_ had admin permissions for + # their own applications so we should only get + # `PermissionDenied` for applications launched after + # organizations were introduced. + assert organization_name is not None + terminal.fail( + "🛑 failed:\n" + f" User '{user_id}' does not have permission to bring up " + f"applications in the organization '{organization_name}', " + "only owners and editors are allowed." + ) + else: + print(f"🛑 unexpected error: {aborted}") + traceback.print_exc() + terminal.fail("Please report this bug to the maintainers") + + async for status_response in application.reactively().RevisionStatus( + context, revision_number=up_response.revision_number + ): + revision = status_response.revision + if revision.status == Status.UPPING: + # Keep waiting. + continue + + if revision.status == Status.UP: + url = _application_url( + ApplicationId(up_response.application_id), + args.cloud_url, + ) + terminal.info( + f"✅\n" + "\n" + f" '{args.application_name}' revision {revision.number} is " + "available:\n" + "\n" + f" Your API is available at: {url}\n" + f" MCP clients can connect at: {url}/mcp\n" + " You can inspect your state at: " + f"{url}/__/inspect\n" + ) + return 0 + + if revision.status == Status.FAILED: + terminal.error( + "🛑 failed:\n" + f"Could not deploy revision {revision.number}:\n" + f" {revision.failure_reason}\n" + "\n" + f"### Logs for revision {revision.number} ###" + ) + await _cloud_logs( + user_id=user_id, + organization_name=organization_name, + qualified_application_name=qualified_application_name, + source=SourceType.CONFIG, + name=args.application_name, + cloud_url=args.cloud_url, + api_key=args.api_key, + revisions=f"{revision.number}", + last=None, # Show all logs for this revision. + show_revision=False, + show_source=False, + show_timestamp=False, + follow=False, + ) + terminal.error( + f"### End of logs for revision {revision.number} ###\n" + "Please correct the issue and try again." + ) + # ISSUE(https://github.com/reboot-dev/mono/issues/4501): don't exit + # with `sys.exit(1)` here; it causes an unexplained stack trace. + # Simply return the error code instead and do a `sys.exit()` in the + # caller, when the `asyncio.run()` is done. + return 1 + + if revision.status == Status.DOWNING or revision.status == Status.DOWN: + terminal.fail( + "🛑 failed:\n" + " The application is being `down`ed. Please wait until that " + "operation completes." + ) + + # A revision that we `Up()`ed will never be in the `DOWNING` or `DOWN` + # state. Those only appear for revisions created by calling `Down()`. + raise ValueError( + f"Application reached an unexpected status: '{revision.status}'. " + "Please report this bug to the maintainers." + ) + + # Should be unreachable, but need for the type checking. + return 1 + + +async def _docker_build_and_push( + dockerfile: Path, + tag: str, + registry_endpoint: str, + registry_username: str, + registry_password: str, + docker_build_args: Optional[list[str]] = None, +) -> str: + """ + Builds and pushes an image with the given `tag` from the `dockerfile`. + + Returns the digest of the pushed image. + """ + assert dockerfile.is_absolute() + if not dockerfile.exists() or not dockerfile.is_file(): + terminal.fail(f"🛑 Could not find Dockerfile '{dockerfile}'") + + dockerfile_pretty = str(dockerfile) + try: + dockerfile_pretty = str(dockerfile.relative_to(Path.cwd())) + if not dockerfile_pretty.startswith("."): + dockerfile_pretty = f"./{dockerfile_pretty}" + except ValueError: + # This means the Dockerfile is not in the current working directory. + # That's OK, we'll simply use the absolute path. + pass + + await run_command( + command=[ + "docker", + "buildx", + "build", + # Reboot Cloud runs on AMD64, so its images must be built for that + # platform. + "--platform", + "linux/amd64", + "--file", + str(dockerfile), + "--tag", + tag, + ] + [f"--build-arg={arg}" for arg in docker_build_args or []] + [ + ".", + ], + cwd=str(dockerfile.parent), + icon="🐳", + command_name="build", + explanation=f"building container from '{dockerfile_pretty}'", + capture_output=False, + ) + + try: + # The push step we do with config from a temporary directory, which + # allows us to use a one-time Docker `config.json`. That avoids + # permanently storing the user's credentials in their normal + # `~/.docker/config.json`. + with tempfile.TemporaryDirectory() as tempdir: + # Create a temporary Docker configuration file. + docker_config = Path(tempdir) / "config.json" + docker_config.write_text( + json.dumps( + { + "auths": + { + registry_endpoint: + { + "auth": + base64.b64encode( + f"{registry_username}:{registry_password}" + .encode() + ).decode(), + } + } + } + ) + ) + + # Push the image with the temporary Docker configuration. + await run_command( + command=[ + "docker", + "--config", + str(tempdir), + "push", + tag, + ], + icon="🚛", + command_name="push", + explanation="pushing container", + capture_output=False, + ) + + # Obtain the image digest. + digest = ( + await run_command( + command=[ + "docker", + "inspect", + "--format", + "{{index .RepoDigests 0}}", + tag, + ], + icon="👀", + command_name="inspect", + explanation="inspecting container", + capture_output=True, + only_show_if_verbose=True, + ) + ).rsplit("@", 1)[-1] + + return digest + + finally: + # Remove the tag from the local Docker daemon so that it doesn't pollute the + # user's list of images. + await run_command( + command=[ + "docker", + "image", + "remove", + tag, + ], + icon="🧹", + command_name="cleanup", + explanation="cleaning up", + capture_output=False, + only_show_if_verbose=True, + ) diff --git a/reboot/cli/dev.py b/reboot/cli/dev.py index 14148991..a25195b1 100644 --- a/reboot/cli/dev.py +++ b/reboot/cli/dev.py @@ -27,7 +27,6 @@ # We import the whole `terminal` module (as opposed to the methods it contains) # to allow us to mock these methods out in tests. from reboot.cli import terminal -from reboot.cli.cloud import add_cloud_options from reboot.cli.directories import ( add_working_directory_options, dot_rbt_dev_directory, @@ -55,14 +54,11 @@ ENVVAR_LOCAL_ENVOY_TLS_CERTIFICATE_PATH, ENVVAR_LOCAL_ENVOY_TLS_KEY_PATH, ENVVAR_LOCAL_ENVOY_USE_TLS, - ENVVAR_RBT_CLOUD_API_KEY, - ENVVAR_RBT_CLOUD_URL, ENVVAR_RBT_DEV, ENVVAR_RBT_EFFECT_VALIDATION, ENVVAR_RBT_MCP_FRONTEND_HOST, ENVVAR_RBT_NAME, ENVVAR_RBT_NODEJS, - ENVVAR_RBT_SECRETS_DIRECTORY, ENVVAR_RBT_SERVERS, ENVVAR_RBT_STATE_DIRECTORY, ENVVAR_REBOOT_LOCAL_ENVOY, @@ -128,17 +124,6 @@ def add_application_options(subcommand: SubcommandParser) -> None: "passing it as an argument to 'node'", ) - subcommand.add_argument( - "--secrets-directory", - type=Path, - default=None, - help=( - "a directory to use to override the default source (environment variables) of Secrets; " - "in the Reboot Cloud, Secrets are written using `rbt cloud secret write`; " - f"see {DOCS_BASE_URL}/develop/secrets for more information." - ), - ) - subcommand.add_argument( '--application', type=str, # TODO: consider argparse.FileType('e') @@ -148,6 +133,10 @@ def add_application_options(subcommand: SubcommandParser) -> None: ) +def dev_subcommands() -> list[str]: + return ['dev expunge', 'dev run'] + + def register_dev(parser: ArgumentParser): _register_dev_run(parser) _register_dev_expunge(parser) @@ -159,7 +148,7 @@ def _register_dev_run(parser: ArgumentParser): add_application_options(parser.subcommand('dev run')) parser.subcommand('dev run').add_argument( - '--name', + '--application-name', type=str, help=( "name of application; state will be persisted using this name in " @@ -167,6 +156,8 @@ def _register_dev_run(parser: ArgumentParser): ), non_empty_string=True, ) + parser.subcommand('dev run' + ).add_renamed_flag('--name', '--application-name') parser.subcommand('dev run').add_argument( '--background-command', @@ -300,10 +291,6 @@ def _register_dev_run(parser: ArgumentParser): help="path to TLS root certificate to use", ) - # The `dev` command does not require an API key, since not everyone will - # have access to secrets on day one. - add_cloud_options(parser.subcommand('dev run'), api_key_required=False) - parser.subcommand('dev run').add_argument( '--tracing', type=str, @@ -315,7 +302,7 @@ def _register_dev_run(parser: ArgumentParser): def _register_dev_expunge(parser: ArgumentParser): parser.subcommand('dev expunge').add_argument( - '--name', + '--application-name', type=str, help=( "name of the application to expunge; will remove this " @@ -324,6 +311,8 @@ def _register_dev_expunge(parser: ArgumentParser): required=True, non_empty_string=True, ) + parser.subcommand('dev expunge' + ).add_renamed_flag('--name', '--application-name') parser.subcommand('dev expunge').add_argument( '--yes', @@ -1266,18 +1255,15 @@ async def __dev_run( env[ENVVAR_RBT_DEV] = 'true' - if args.name is not None: - env[ENVVAR_RBT_NAME] = args.name + if args.application_name is not None: + env[ENVVAR_RBT_NAME] = args.application_name # Use a state directory specific to the application name. For some # applications there may be multiple servers, each with their own # subdirectory. env[ENVVAR_RBT_STATE_DIRECTORY] = str( - dot_rbt_dev_directory(args, parser) / args.name + dot_rbt_dev_directory(args, parser) / args.application_name ) - if args.secrets_directory is not None: - env[ENVVAR_RBT_SECRETS_DIRECTORY] = args.secrets_directory - if args.mcp_frontend_host: env[ENVVAR_RBT_MCP_FRONTEND_HOST] = args.mcp_frontend_host @@ -1339,15 +1325,13 @@ async def __dev_run( EffectValidation, ).name - if args.api_key is not None: - env[ENVVAR_RBT_CLOUD_API_KEY] = args.api_key - env[ENVVAR_RBT_CLOUD_URL] = args.cloud_url - # Set a signing secret for the MCP OAuth server. For local # dev we use the application name (insecure, but convenient). # Fall back to a fixed string when `--name` wasn't provided. if ENVVAR_REBOOT_OAUTH_SIGNING_SECRET not in env: - env[ENVVAR_REBOOT_OAUTH_SIGNING_SECRET] = args.name or "reboot-dev" + env[ENVVAR_REBOOT_OAUTH_SIGNING_SECRET] = ( + args.application_name or "reboot-dev" + ) if tracing == Tracing.JAEGER: # TODO: dynamic port. See comment in `_run_jaeger()`. @@ -1401,15 +1385,15 @@ async def __dev_run( # Determine the appropriate verb. start_verb = "Starting" if first_start else "Restarting" - if args.name is None: + if args.application_name is None: terminal.warn( f'{start_verb} an ANONYMOUS application; to reuse state ' - 'across application restarts use --name' + 'across application restarts use --application-name' '\n' ) else: terminal.info( - f'{start_verb} application with name "{args.name}"...' + f'{start_verb} application with name "{args.application_name}"...' '\n' ) first_start = False @@ -1482,7 +1466,7 @@ async def __dev_run( bundle = await auto_transpile( subprocesses, application, - args.name or "anonymous", + args.application_name or "anonymous", ts_input_paths, ) @@ -1729,21 +1713,25 @@ def ask_for_confirmation(question: str) -> bool: dot_rbt_dev = dot_rbt_dev_directory(args, parser) if confirm and args.yes is False: - terminal.info(f"About to expunge '{args.name}' from '{dot_rbt_dev}'") + terminal.info( + f"About to expunge '{args.application_name}' from '{dot_rbt_dev}'" + ) if not ask_for_confirmation( "Do you want to continue? [y/n] (Tip: Use the --yes flag to skip this prompt):" ): terminal.fail("Expunge cancelled") - application_directory = dot_rbt_dev / args.name + application_directory = dot_rbt_dev / args.application_name if not application_directory.exists(): terminal.warn( - f"Could not find application with name '{args.name}' (looked in " + f"Could not find application with name '{args.application_name}' (looked in " f"'{application_directory}'); did not expunge" ) return await asyncio.to_thread(shutil.rmtree, application_directory) - terminal.info(f"Application '{args.name}' has been expunged.\n") + terminal.info( + f"Application '{args.application_name}' has been expunged.\n" + ) async def dev_expunge( @@ -1755,3 +1743,21 @@ async def dev_expunge( """ await _expunge(args, parser, confirm=True) + + +async def handle_dev_subcommand( + args: argparse.Namespace, + *, + parser: ArgumentParser, + parser_factory: ArgumentParserFactory, +) -> Optional[int]: + if args.subcommand == 'dev run': + return await dev_run( + args, + parser=parser, + parser_factory=parser_factory, + ) + elif args.subcommand == 'dev expunge': + await dev_expunge(args, parser) + return 0 + return None diff --git a/reboot/cli/export_import.py b/reboot/cli/export_import.py index 3d3cbbb2..a053214e 100644 --- a/reboot/cli/export_import.py +++ b/reboot/cli/export_import.py @@ -6,6 +6,11 @@ from reboot.aio.external import ExternalContext from reboot.cli import terminal from reboot.cli.rc import ArgumentParser, add_common_channel_args +from typing import Optional + + +def export_and_import_subcommands() -> list[str]: + return ['export', 'import'] def register_export_and_import(parser: ArgumentParser): @@ -97,3 +102,13 @@ async def do_import(args: argparse.Namespace) -> int: terminal.info(f"Imported from: `{src_dir}`") return 0 + + +async def handle_export_and_import_subcommand( + args: argparse.Namespace, +) -> Optional[int]: + if args.subcommand == 'export': + return await do_export(args) + elif args.subcommand == 'import': + return await do_import(args) + return None diff --git a/reboot/cli/generate.py b/reboot/cli/generate.py index a0b0eea4..7bf0bb88 100644 --- a/reboot/cli/generate.py +++ b/reboot/cli/generate.py @@ -1,4 +1,5 @@ import aiofiles.os +import argparse import asyncio import glob import os @@ -99,6 +100,10 @@ ).lower() == "true" +def generate_subcommands() -> list[str]: + return ['generate'] + + def register_generate(parser: ArgumentParser): add_working_directory_options(parser.subcommand('generate')) @@ -367,6 +372,17 @@ async def generate( ) +async def handle_generate_subcommand( + args: argparse.Namespace, + *, + argv_after_dash_dash: list[str], + parser: ArgumentParser, +) -> Optional[int]: + if args.subcommand == 'generate': + return await generate(args, argv_after_dash_dash, parser=parser) + return None + + @reboot.aio.tracing.function_span() async def generate_direct( args, @@ -789,7 +805,7 @@ def add_es_opts( if os.stat(file).st_size == 0: terminal.error( f"'{file}' is empty. " - f"See {DOCS_BASE_URL}/develop/schema for " + f"See {DOCS_BASE_URL}/learn_more/define/protobuf for " "more information on filling out your proto file." ) # Return an error status here to not break the 'rbt dev' loop. @@ -823,7 +839,7 @@ def add_es_opts( if os.stat(file).st_size == 0: terminal.error( f"'{file}' is empty. " - f"See {DOCS_BASE_URL}/develop/schema for " + f"See {DOCS_BASE_URL}/learn_more/define/protobuf for " "more information on filling out your schema in a '.ts' file." ) # Return an error status here to not break the 'rbt dev' loop. @@ -962,7 +978,7 @@ def add_es_opts( if os.stat(file).st_size == 0: terminal.error( f"'{file}' is empty. " - f"See {DOCS_BASE_URL}/develop/schema for " + f"See {DOCS_BASE_URL}/learn_more/define/protobuf for " "more information on filling out your schema in a '.py' file." ) # Return an error status here to not break the 'rbt dev' loop. diff --git a/reboot/cli/init/init.py b/reboot/cli/init/init.py index 8164a8ac..0d9d5b4d 100644 --- a/reboot/cli/init/init.py +++ b/reboot/cli/init/init.py @@ -1,10 +1,12 @@ import aiofiles.os +import argparse import importlib import os import re from jinja2 import Environment, FileSystemLoader, select_autoescape from reboot.cli import terminal from reboot.cli.rc import ArgumentParser +from typing import Optional DEFAULT_PROTO_DIRECTORY = 'api' DEFAULT_BACKEND_DIRECTORY = 'backend' @@ -40,6 +42,10 @@ DEFAULT_FRONTEND = FRONTEND[0] +def init_subcommands() -> list[str]: + return ['init'] + + def register_init(parser: ArgumentParser): parser.subcommand('init').add_argument( '--backend', @@ -58,11 +64,12 @@ def register_init(parser: ArgumentParser): ) parser.subcommand('init').add_argument( - '--name', + '--application-name', type=str, help="name of application; should be in lower_snake_case", required=True, ) + parser.subcommand('init').add_renamed_flag('--name', '--application-name') async def _write_templated_file( @@ -83,7 +90,7 @@ async def _write_templated_file( async def _initialize_python(env: Environment, directory: str, args): template_data = { - 'name': args.name, + 'name': args.application_name, } await _write_templated_file( @@ -124,7 +131,7 @@ async def _initialize_nodejs(env: Environment, directory: str, args): directory, 'package.json', template_data={ - 'name': args.name, + 'name': args.application_name, }, ) @@ -142,7 +149,7 @@ async def _initialize_nodejs(env: Environment, directory: str, args): f'{directory}/{DEFAULT_BACKEND_SRC_DIRECTORY}', NODEJS_MAIN_TEMPLATE.replace('.j2', ''), template_data={ - 'name': args.name, + 'name': args.application_name, 'api_directory': DEFAULT_PROTO_DIRECTORY, }, ) @@ -153,7 +160,7 @@ async def _initialize_nodejs(env: Environment, directory: str, args): f'{directory}/{DEFAULT_BACKEND_SRC_DIRECTORY}', NODEJS_SERVICER_TEMPLATE.replace('.j2', ''), template_data={ - 'name': args.name, + 'name': args.application_name, 'api_directory': DEFAULT_PROTO_DIRECTORY, }, ) @@ -223,7 +230,7 @@ async def _initialize_react(env: Environment, directory: str, args): template_data = { 'reboot_react_path': - f"./{DEFAULT_PROTO_DIRECTORY}/{args.name}/v1/{PROTO_TEMPLATE.replace('.j2', '').replace('.proto', '_rbt_react')}", + f"./{DEFAULT_PROTO_DIRECTORY}/{args.application_name}/v1/{PROTO_TEMPLATE.replace('.j2', '').replace('.proto', '_rbt_react')}", } await _write_templated_file( @@ -244,13 +251,13 @@ async def _initialize_react(env: Environment, directory: str, args): async def _initialize_proto_directory(env: Environment, directory: str, args): template_data = { - 'name': args.name, + 'name': args.application_name, } await _write_templated_file( env, PROTO_TEMPLATE, - f'{directory}/{DEFAULT_PROTO_DIRECTORY}/{args.name}/v1', + f'{directory}/{DEFAULT_PROTO_DIRECTORY}/{args.application_name}/v1', PROTO_TEMPLATE.replace('.j2', ''), template_data, ) @@ -267,7 +274,7 @@ async def _create_rbtrc(env: Environment, directory: str, args): 'web_directory': DEFAULT_WEB_SRC_DIRECTORY if args.frontend == 'react' else None, 'name': - args.name, + args.application_name, 'backend': args.backend, } @@ -294,7 +301,7 @@ def validate_name(name: str): async def init_run(args): - validate_name(args.name) + validate_name(args.application_name) directory = os.getcwd() @@ -304,11 +311,13 @@ async def init_run(args): # it will clash with the project name, since we don't provide # '__init__.py' files in the generated code, so we force users to pick # a different name. - module = importlib.import_module(args.name) # noqa: F841 + module = importlib.import_module( # noqa: F841 + args.application_name + ) terminal.fail( - f"Can't initialize your Python backend: the name '{args.name}' " - f"would clash with the Python standard library '{args.name}' " + f"Can't initialize your Python backend: the name '{args.application_name}' " + f"would clash with the Python standard library '{args.application_name}' " "module. Try a different name." ) except ModuleNotFoundError: @@ -344,3 +353,12 @@ async def init_run(args): if args.frontend == 'react': await _initialize_react(env, directory, args) + + +async def handle_init_subcommand( + args: argparse.Namespace, +) -> Optional[int]: + if args.subcommand == 'init': + await init_run(args) + return 0 + return None diff --git a/reboot/cli/init/nodejs_test.sh b/reboot/cli/init/nodejs_test.sh index 4c61f630..d9560cb5 100755 --- a/reboot/cli/init/nodejs_test.sh +++ b/reboot/cli/init/nodejs_test.sh @@ -67,7 +67,7 @@ RBT_FLAGS="--state-directory=$(mktemp -d)" if [ -n "${EXPECTED_RBT_DEV_OUTPUT_FILE:-}" ]; then mkdir init_nodejs_test && cd init_nodejs_test - npx rbt $RBT_FLAGS init --name=bazel_init_nodejs_test --backend=nodejs + npx rbt $RBT_FLAGS init --application-name=bazel_init_nodejs_test --backend=nodejs # As per the instructions from running `rbt init`, we now need to # run `npm install` but making sure to get the unreleased Reboot diff --git a/reboot/cli/init/templates/.rbtrc.j2 b/reboot/cli/init/templates/.rbtrc.j2 index 5cb805fe..e4ce70a4 100644 --- a/reboot/cli/init/templates/.rbtrc.j2 +++ b/reboot/cli/init/templates/.rbtrc.j2 @@ -56,7 +56,7 @@ dev run --python dev run --application=backend/src/main.py {% endif %} # Save state between restarts. -dev run --name={{ name }} +dev run --application-name={{ name }} # When expunging, expunge that state we've saved. -dev expunge --name={{ name }} +dev expunge --application-name={{ name }} diff --git a/reboot/cli/init/templates/backend_package.json.j2 b/reboot/cli/init/templates/backend_package.json.j2 index b2d14e79..a0034cf9 100644 --- a/reboot/cli/init/templates/backend_package.json.j2 +++ b/reboot/cli/init/templates/backend_package.json.j2 @@ -4,7 +4,7 @@ "private": true, "type": "module", "dependencies": { - "@reboot-dev/reboot": "0.46.0", + "@reboot-dev/reboot": "1.0.3", "typescript": "^5.5.2" } } diff --git a/reboot/cli/init/templates/package.json.j2 b/reboot/cli/init/templates/package.json.j2 index 765f522c..e931687b 100644 --- a/reboot/cli/init/templates/package.json.j2 +++ b/reboot/cli/init/templates/package.json.j2 @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@reboot-dev/reboot-react": "0.46.0", + "@reboot-dev/reboot-react": "1.0.3", "@types/jest": "^27.5.2", "@types/node": "^20.11.5", "@types/react": "^19.2.1", diff --git a/reboot/cli/init/test.sh b/reboot/cli/init/test.sh index 4c262c83..84032229 100755 --- a/reboot/cli/init/test.sh +++ b/reboot/cli/init/test.sh @@ -44,7 +44,7 @@ rye add --dev reboot --absolute --path=$REBOOT_WHL_FILE rye sync --no-lock source .venv/bin/activate -rbt init --name=bazel_init_test +rbt init --application-name=bazel_init_test rbt generate diff --git a/reboot/cli/rc.py b/reboot/cli/rc.py index 6860ae3a..25314dc1 100644 --- a/reboot/cli/rc.py +++ b/reboot/cli/rc.py @@ -366,6 +366,99 @@ def __init__( super().__init__(parser=parser, cwd=cwd) self._subcommand = subcommand + def add_renamed_flag( + self, + old_flag: str, + new_flag: str, + ) -> None: + """ + Register a renamed flag's old name as an alias. + + When the old flag is used, a warning is printed directing the + user to the new name, but no error occurs. Both flags store + their input under the same name (derived from `new_flag`). + """ + # Derive `dest` from `new_flag` the same way argparse does. + dest = new_flag.lstrip('-').replace('-', '_') + + # Find the primary action so we can share its duplicate- + # tracking list. This ensures that using both the old and + # new flag is detected as a duplicate. + primary_action = None + for action in self._parser._actions: + if ( + action.dest == dest and + isinstance(action, StoreOnceActionBase) + ): + primary_action = action + break + + assert primary_action is not None, ( + f"add_renamed_flag('{old_flag}', '{new_flag}'): " + f"'{new_flag}' must be registered before its " + f"old name '{old_flag}'" + ) + + # Bind to a local so mypy knows it's non-None inside the + # closure. + seen_list = primary_action._already_seen_for_namespace + + # Capture `new_flag`, `seen_list`, and `primary_action` in a + # local class so argparse can instantiate it (argparse expects + # `type[Action]` for `action=`, not a factory function, so we + # can't pass these three values as constructor args). + _primary_action = primary_action + + class _RenamedFlag(StoreOnceActionBase): + + def __init__(self, *args: Any, **kwargs: Any): + super().__init__(*args, **kwargs) + # Share the same seen-tracking list so that using both + # the old and the new flag at the same time is detected + # as a duplicate. + self._already_seen_for_namespace = seen_list + + def __call__(self, parser, namespace, values, option_string=None): + terminal.warn( + f"'{option_string}' has been renamed to " + f"'{new_flag}'. Please switch to using " + f"'{new_flag}', as '{option_string}' will " + f"be removed in the future." + ) + # Satisfy the `required` check for the primary action + # (the new flag) so that, assuming the flag is required, + # passing the old name still satisfies this requirement. + _primary_action.required = False + if self._is_duplicate(namespace): + parser.error( + f"the flag '{new_flag}' (previously " + f"'{option_string}') was set multiple times; " + "it can only be set once, including " + "in the `.rbtrc` file" + ) + else: + self._mark_seen_and_store(namespace, values) + + self._parser.add_argument( + old_flag, + dest=dest, + action=_RenamedFlag, + help=argparse.SUPPRESS, + ) + + # Mirror the new flag's type metadata so that the + # `--flag=VALUE` enforcement and path normalization also + # apply to the old flag name. + new_flag_type = self._argument_types.get(new_flag) + if new_flag_type is not None: + self._argument_types[old_flag] = new_flag_type + + new_flag_dest = '--' + dest + new_flag_transformer = self._transformers.get(new_flag_dest) + if new_flag_transformer is not None: + old_flag_dest = '--' + old_flag.lstrip('-').replace('-', '_') + self._transformers[old_flag_dest] = new_flag_transformer + def get_subparsers(self) -> argparse._SubParsersAction: """Create subcommand parser for the subcommand.""" if self._subparsers is None: @@ -436,9 +529,9 @@ class ArgumentParser(_Parser): (6) All lines are aggregated. - dev:demo --config=fullstack # The demo is "fullstack". - dev:demo --name=demo # Name of demo. - dev:demo --python # Uses Python. + dev:demo --config=fullstack # The demo is "fullstack". + dev:demo --application-name=demo # Name of demo. + dev:demo --python # Uses Python. TODO(benh): move this into own file 'rc_argument_parser.py' diff --git a/reboot/cli/secret.py b/reboot/cli/secret.py deleted file mode 100644 index d54617df..00000000 --- a/reboot/cli/secret.py +++ /dev/null @@ -1,50 +0,0 @@ -from pathlib import Path -from reboot.cli import terminal -from reboot.cli.cloud import add_cloud_options -from reboot.cli.rc import ArgumentParser - - -def register_secret(parser: ArgumentParser): - - def add_common_args(subcommand): - add_cloud_options(subcommand, api_key_required=True) - subcommand.add_argument( - '--secret-name', - type=str, - required=True, - help="name of the secret", - ) - - secret_write = parser.subcommand('cloud secret write') - add_common_args(secret_write) - # TODO: These two options should be a mutually exclusive group, but that - # facility is not exposed by `rc.py`. - secret_write.add_argument( - '--secret-value', - type=str, - help= - "the secret value to store; the value will be UTF8 encoded for storage", - ) - secret_write.add_argument( - '--secret-value-file', - type=Path, - help="a file containing a secret value to store", - ) - - add_common_args(parser.subcommand('cloud secret delete')) - - -async def secret_write(args) -> None: - """Implementation of the 'cloud secret write' subcommand.""" - terminal.fail( - "Secrets are not supported on Reboot Cloud yet. Please reach out and " - "let us know about your use-case and we'll prioritize this feature!" - ) - - -async def secret_delete(args) -> None: - """Implementation of the 'cloud secret delete' subcommand.""" - terminal.fail( - "Secrets are not supported on Reboot Cloud yet. Please reach out and " - "let us know about your use-case and we'll prioritize this feature!" - ) diff --git a/reboot/cli/serve.py b/reboot/cli/serve.py index 0e4f7432..bd1c239a 100644 --- a/reboot/cli/serve.py +++ b/reboot/cli/serve.py @@ -1,4 +1,5 @@ import aiofiles.os +import argparse import asyncio import math import os @@ -40,7 +41,6 @@ ENVVAR_RBT_EFFECT_VALIDATION, ENVVAR_RBT_NAME, ENVVAR_RBT_NODEJS, - ENVVAR_RBT_SECRETS_DIRECTORY, ENVVAR_RBT_SERVE, ENVVAR_RBT_SERVERS, ENVVAR_RBT_STATE_DIRECTORY, @@ -166,6 +166,10 @@ async def _pick_envoy_mode( ) +def serve_subcommands() -> list[str]: + return ['serve run'] + + def register_serve(parser: ArgumentParser): add_working_directory_options(parser.subcommand('serve run')) @@ -195,12 +199,14 @@ def register_serve(parser: ArgumentParser): ) parser.subcommand('serve run').add_argument( - '--name', + '--application-name', type=str, help="name of application, used to differentiate within " "'--state-directory'", required=True, ) + parser.subcommand('serve run' + ).add_renamed_flag('--name', '--application-name') parser.subcommand('serve run').add_argument( '--port', @@ -283,16 +289,13 @@ async def serve_run( env[ENVVAR_RBT_SERVE] = 'true' - assert args.name is not None + assert args.application_name is not None - env[ENVVAR_RBT_NAME] = args.name + env[ENVVAR_RBT_NAME] = args.application_name if args.state_directory is not None: env[ENVVAR_RBT_STATE_DIRECTORY] = args.state_directory - if args.secrets_directory is not None: - env[ENVVAR_RBT_SECRETS_DIRECTORY] = args.secrets_directory - # Pick the mode we'll run Envoy in: either as a stand-alone # program or inside a Docker container. await _pick_envoy_mode(subprocesses, env) @@ -360,7 +363,7 @@ async def serve_run( bundle = await auto_transpile( subprocesses, application, - args.name, + args.application_name, [], ) @@ -391,3 +394,18 @@ async def serve_run( async with subprocesses.exec(*args, env=env) as process: return await process.wait() + + +async def handle_serve_subcommand( + args: argparse.Namespace, + *, + parser: ArgumentParser, + parser_factory: ArgumentParserFactory, +) -> Optional[int]: + if args.subcommand == 'serve run': + return await serve_run( + args, + parser=parser, + parser_factory=parser_factory, + ) + return None diff --git a/reboot/cli/task.py b/reboot/cli/task.py index 21d364e7..93dd9715 100644 --- a/reboot/cli/task.py +++ b/reboot/cli/task.py @@ -1,3 +1,4 @@ +import argparse import grpc import re import uuid @@ -12,6 +13,10 @@ from typing import Optional +def task_subcommands() -> list[str]: + return ['task list', 'task cancel'] + + def register_task(parser: ArgumentParser): """Register the 'task' subcommand with the given parser.""" @@ -271,3 +276,15 @@ async def task_cancel(args) -> None: terminal.fail("Task is cancelling.") except grpc.aio.AioRpcError as e: terminal.fail(f"Failed to cancel task: {e.details()}") + + +async def handle_task_subcommand( + args: argparse.Namespace, +) -> Optional[int]: + if args.subcommand == 'task list': + await task_list(args) + return 0 + elif args.subcommand == 'task cancel': + await task_cancel(args) + return 0 + return None diff --git a/reboot/create-ui/.gitignore b/reboot/create-ui/.gitignore new file mode 100644 index 00000000..1a993212 --- /dev/null +++ b/reboot/create-ui/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +package-lock.json diff --git a/reboot/create-ui/BUILD.bazel b/reboot/create-ui/BUILD.bazel new file mode 100644 index 00000000..1d55cb19 --- /dev/null +++ b/reboot/create-ui/BUILD.bazel @@ -0,0 +1,60 @@ +load("@aspect_rules_esbuild//esbuild:defs.bzl", "esbuild") +load("@aspect_rules_js//npm:defs.bzl", "npm_package") +load("@com_github_reboot_dev_reboot//reboot:versions.bzl", "REBOOT_VERSION") + +esbuild( + name = "bundle", + srcs = glob([ + "src/*.ts", + "templates/*.tmpl", + ]), + bazel_sandbox_plugin = False, + config = { + "banner": {"js": "#!/usr/bin/env node"}, + "loader": {".tmpl": "text"}, + }, + entry_point = "src/index.ts", + format = "cjs", + output = "dist/index.js", + platform = "node", +) + +npm_package( + name = "create-ui", + srcs = [ + ":bundle", + ":package.json", + ], + include_runfiles = False, + visibility = ["//visibility:public"], +) + +genrule( + name = "reboot-dev-create-ui", + srcs = [ + "//:README.md", + ":create-ui", + ], + outs = ["reboot-dev-create-ui-" + REBOOT_VERSION + ".tgz"], + cmd = """ + set -eu + npm_binary_path=$$(realpath $(execpath @node//:npm)) + readme_path="$$(realpath $(execpath //:README.md))" + working_folder=$$(dirname $(location :create-ui)) + pushd $$working_folder + mkdir -p build && cd build + cp -r -L ../create-ui/* . + cp $$readme_path README.md + $$npm_binary_path pack --pack-destination . + path=$$(realpath reboot-dev-create-ui-{reboot_version}.tgz) + popd + mv $$path $(@D) + rm -rf $$working_folder/build + """.format( + reboot_version = REBOOT_VERSION, + ), + tools = [ + "@node//:npm", + ], + visibility = ["//visibility:public"], +) diff --git a/reboot/create-ui/package.json b/reboot/create-ui/package.json new file mode 100644 index 00000000..aedaebc9 --- /dev/null +++ b/reboot/create-ui/package.json @@ -0,0 +1,24 @@ +{ + "name": "@reboot-dev/create-ui", + "version": "1.0.3", + "description": "Scaffold React implementation for Reboot AI Chat App UIs", + "type": "commonjs", + "bin": { + "create-ui": "./dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "esbuild src/index.ts --bundle --platform=node --format=cjs --outfile=dist/index.js --banner:js='#!/usr/bin/env node' --loader:.tmpl=text", + "prepublishOnly": "npm run build" + }, + "devDependencies": { + "esbuild": "^0.25.0", + "@types/node": "^22.0.0", + "typescript": "^5.9.2" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/reboot/create-ui/src/discover.ts b/reboot/create-ui/src/discover.ts new file mode 100644 index 00000000..7b99ea33 --- /dev/null +++ b/reboot/create-ui/src/discover.ts @@ -0,0 +1,147 @@ +import fs from "fs"; +import path from "path"; + +/** A single UI from a `_rbt_ui.json` manifest. */ +export interface UiEntry { + name: string; + stateName: string; + path: string; + title: string; +} + +/** A UI entry enriched with its proto package and base name. */ +export interface UiEntryWithPackage extends UiEntry { + package: string; + /** Base name of the proto/API file (e.g. "romeo" from romeo_rbt_ui.json). */ + protoBase: string; +} + +interface UiManifest { + package: string; + uis: UiEntry[]; +} + +/** + * Walk up from `startDir` looking for `.rbtrc` or + * `pyproject.toml` — whichever comes first is the project + * root. + */ +export function findProjectRoot(startDir: string): string | null { + let dir = path.resolve(startDir); + while (true) { + if ( + fs.existsSync(path.join(dir, ".rbtrc")) || + fs.existsSync(path.join(dir, "pyproject.toml")) + ) { + return dir; + } + const parent = path.dirname(dir); + // Reached the filesystem root. + if (parent === dir) return null; + dir = parent; + } +} + +/** + * Parse `.rbtrc` for `generate --react=`. Returns + * the directory relative to the project root, or `null` + * if no `--react` flag is found. + */ +export function parseReactDir(rbtrcPath: string): string | null { + if (!fs.existsSync(rbtrcPath)) return null; + const lines = fs.readFileSync(rbtrcPath, "utf-8").split("\n"); + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.startsWith("#")) continue; + const match = trimmed.match(/^generate\s+--react=(\S+)/); + if (match) return match[1]; + } + return null; +} + +/** Recursively glob for `*_rbt_ui.json` files. */ +function globManifests(dir: string): string[] { + const results: string[] = []; + if (!fs.existsSync(dir)) return results; + + const entries = fs.readdirSync(dir, { + withFileTypes: true, + }); + for (const entry of entries) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + results.push(...globManifests(full)); + } else if (entry.name.endsWith("_rbt_ui.json")) { + results.push(full); + } + } + return results; +} + +export interface DiscoveryResult { + projectRoot: string; + /** React output dir from `.rbtrc`, e.g. `"web/api"`. */ + reactDir: string; + allUis: UiEntryWithPackage[]; + unimplemented: UiEntryWithPackage[]; +} + +/** + * Discover all UIs defined in `_rbt_ui.json` manifests + * and determine which ones are not yet scaffolded. + */ +export function discover(cwd: string): DiscoveryResult | null { + const projectRoot = findProjectRoot(cwd); + if (!projectRoot) return null; + + const rbtrcPath = path.join(projectRoot, ".rbtrc"); + const reactDir = parseReactDir(rbtrcPath); + + // Search in the react output dir, or common defaults. + const searchDirs: string[] = []; + if (reactDir) { + searchDirs.push(path.join(projectRoot, reactDir)); + } + for (const d of ["web/api", "api"]) { + const full = path.join(projectRoot, d); + if (!searchDirs.includes(full)) { + searchDirs.push(full); + } + } + + const manifests: string[] = []; + for (const dir of searchDirs) { + manifests.push(...globManifests(dir)); + } + + if (manifests.length === 0) return null; + + const allUis: UiEntryWithPackage[] = []; + for (const manifestPath of manifests) { + const raw = fs.readFileSync(manifestPath, "utf-8"); + const manifest: UiManifest = JSON.parse(raw); + // Derive proto base from manifest filename: + // e.g. "romeo_rbt_ui.json" -> "romeo" + const baseName = path.basename(manifestPath); + const protoBase = baseName.replace(/_rbt_ui\.json$/, ""); + for (const ui of manifest.uis) { + allUis.push({ + ...ui, + package: manifest.package, + protoBase, + }); + } + } + + const unimplemented = allUis.filter((ui) => { + const indexHtml = path.join(projectRoot, ui.path, "index.html"); + return !fs.existsSync(indexHtml); + }); + + return { + projectRoot, + reactDir: reactDir ?? "web/api", + allUis, + unimplemented, + }; +} diff --git a/reboot/create-ui/src/index.ts b/reboot/create-ui/src/index.ts new file mode 100644 index 00000000..ec2925fe --- /dev/null +++ b/reboot/create-ui/src/index.ts @@ -0,0 +1,71 @@ +import { discover } from "./discover.js"; +import { scaffold } from "./scaffold.js"; + +function main() { + const cwd = process.cwd(); + const result = discover(cwd); + + if (!result) { + console.log( + "No _rbt_ui.json manifests found.\n\n" + + "Run `rbt generate` first to generate " + + "React bindings and UI manifests." + ); + process.exit(1); + } + + const { projectRoot, reactDir, allUis, unimplemented } = result; + + if (unimplemented.length === 0) { + console.log("All UIs are already scaffolded. Good job! \u{1F44D}"); + process.exit(0); + } + + console.log( + `Your API contains ${allUis.length} 'UI' methods, ` + + `${unimplemented.length} of these don't have a ` + + `frontend implementation yet. This tool will ` + + `scaffold those frontend implementations.\n` + ); + + const scaffolded = scaffold(projectRoot, reactDir, allUis, unimplemented); + + if (scaffolded.createdShared.length > 0) { + console.log("Created shared files:"); + for (const f of scaffolded.createdShared) { + console.log(` ${f}`); + } + console.log(); + } + + if (scaffolded.createdUis.length > 0) { + console.log("Scaffolded UIs:"); + for (const f of scaffolded.createdUis) { + console.log(` ${f}`); + } + } + + if (scaffolded.skipped.length > 0) { + const root = scaffolded.webRootDir; + console.log( + `\nWarning: ${scaffolded.skipped.length} UI(s) ` + + `skipped (path not under ${root}/):` + ); + for (const ui of scaffolded.skipped) { + console.log(` ${ui.path} (${ui.name})`); + } + console.log( + ` UIs must use path="${root}/ui/" ` + + `to share the vite dev server.` + ); + } + + console.log( + `\nNext steps:\n` + + ` cd ${scaffolded.webRootDir} && npm install\n` + + ` rbt generate ` + + `# React bindings need node_modules` + ); +} + +main(); diff --git a/reboot/create-ui/src/scaffold.ts b/reboot/create-ui/src/scaffold.ts new file mode 100644 index 00000000..c4ae7165 --- /dev/null +++ b/reboot/create-ui/src/scaffold.ts @@ -0,0 +1,106 @@ +import fs from "fs"; +import path from "path"; +import type { UiEntryWithPackage } from "./discover.js"; +import * as templates from "./templates.js"; + +/** Write a file only if it does not already exist. */ +function writeIfMissing(filePath: string, content: string): boolean { + if (fs.existsSync(filePath)) return false; + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, content); + return true; +} + +/** + * Derive the web root from the react output directory. + * `.rbtrc` has `generate --react=web/api` — the web root + * is the first path component (e.g. `web`). + */ +function webRoot(reactDir: string): string { + return reactDir.split("/")[0]; +} + +function projectName(projectRoot: string): string { + return path.basename(projectRoot); +} + +export interface ScaffoldResult { + webRootDir: string; + createdShared: string[]; + createdUis: string[]; + /** UIs whose paths don't match the web root. */ + skipped: UiEntryWithPackage[]; +} + +/** + * Scaffold shared web root files and per-UI files for + * unimplemented UIs. All UIs must live under the web + * root derived from `--react=` in `.rbtrc`. + */ +export function scaffold( + projectRoot: string, + reactDir: string, + allUis: UiEntryWithPackage[], + unimplemented: UiEntryWithPackage[] +): ScaffoldResult { + const root = webRoot(reactDir); + const webDir = path.join(projectRoot, root); + const name = projectName(projectRoot); + + const skipped = unimplemented.filter((ui) => ui.path.split("/")[0] !== root); + const toScaffold = unimplemented.filter( + (ui) => ui.path.split("/")[0] === root + ); + + const createdShared: string[] = []; + const createdUis: string[] = []; + + // Derive UI directory names for package.json build scripts. + // Use the last path component (e.g. "web/ui/profile" -> "profile") + // to match what the vite config discovers under ui/. + const uiNames = allUis + .filter((ui) => ui.path.split("/")[0] === root) + .map((ui) => path.basename(ui.path)); + + // Shared files (written once, never updated). + const sharedFiles: Array<[string, string]> = [ + ["package.json", templates.packageJson(name, uiNames)], + ["vite.config.ts", templates.viteConfig()], + ["tsconfig.json", templates.tsconfigJson()], + ["tsconfig.app.json", templates.tsconfigAppJson()], + ["tsconfig.node.json", templates.tsconfigNodeJson()], + ]; + + for (const [file, content] of sharedFiles) { + const filePath = path.join(webDir, file); + if (writeIfMissing(filePath, content)) { + createdShared.push(path.relative(projectRoot, filePath)); + } + } + + // Per-UI files. + for (const ui of toScaffold) { + const uiDir = path.join(projectRoot, ui.path); + const files: Array<[string, string]> = [ + ["index.html", templates.indexHtml(ui)], + ["main.tsx", templates.mainTsx(ui)], + ["App.tsx", templates.appTsx(ui)], + ["App.module.css", templates.appModuleCss()], + ["index.css", templates.indexCss()], + ]; + + for (const [file, content] of files) { + const filePath = path.join(uiDir, file); + if (writeIfMissing(filePath, content)) { + createdUis.push(path.relative(projectRoot, filePath)); + } + } + } + + return { + webRootDir: root, + createdShared, + createdUis, + skipped, + }; +} diff --git a/reboot/create-ui/src/templates.ts b/reboot/create-ui/src/templates.ts new file mode 100644 index 00000000..5ca3d768 --- /dev/null +++ b/reboot/create-ui/src/templates.ts @@ -0,0 +1,206 @@ +import type { UiEntryWithPackage } from "./discover.js"; + +// Templates imported as strings via esbuild's text loader +// (`--loader:.tmpl=text`). Parameterized templates use +// `{{variable}}` placeholders filled by `render()`. +import viteConfigTmpl from "../templates/vite.config.ts.tmpl"; +import indexCssTmpl from "../templates/index.css.tmpl"; +import appModuleCssTmpl from "../templates/App.module.css.tmpl"; +import indexHtmlTmpl from "../templates/index.html.tmpl"; +import mainTsxTmpl from "../templates/main.tsx.tmpl"; +import appTsxTmpl from "../templates/App.tsx.tmpl"; + +/** Replace `{{key}}` placeholders with values. */ +function render(template: string, vars: Record): string { + return template.replace( + /\{\{(\w+)\}\}/g, + (_, key) => vars[key] ?? `{{${key}}}` + ); +} + +/** Convert snake_case to PascalCase. */ +function pascalCase(s: string): string { + return s + .split("_") + .map((w) => w.charAt(0).toUpperCase() + w.slice(1)) + .join(""); +} + +/** Exported app component name, e.g. `"ShowClickerApp"`. */ +function appName(ui: UiEntryWithPackage): string { + return `${pascalCase(ui.name)}App`; +} + +// ── Shared files (web root) ──────────────────────────── + +/** + * Generate web/package.json with per-UI build scripts. + * Each UI gets a `build:` and `build:watch:` + * script, and the top-level `build` chains them all. + */ +export function packageJson( + projectName: string, + uiNames: string[] = [] +): string { + const scripts: Record = { + dev: "vite", + }; + + for (const name of uiNames) { + scripts[`build:${name}`] = `vite build --mode ${name}`; + scripts[`build:watch:${name}`] = `vite build --mode ${name} --watch`; + } + + const buildChain = uiNames + .map((name) => `npm run build:${name}`) + .join(" && "); + scripts.build = buildChain ? `tsc --noEmit && ${buildChain}` : "tsc --noEmit"; + scripts["build:watch"] = 'concurrently "npm:build:watch:*"'; + + const pkg = { + name: `${projectName}-web`, + version: "0.1.0", + private: true, + type: "module", + scripts, + // Version pins below are synced by `make versions` from + // `reboot/versions.bzl` (@reboot-dev/*) and the workspace root + // `package.json` (@modelcontextprotocol/*). Hand edits get + // overwritten; change the source of truth instead. + dependencies: { + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-react": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", + react: "^18.2.0", + "react-dom": "^18.2.0", + zod: "^3.25.0", + }, + devDependencies: { + "@types/react": "^18.2.67", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.7.0", + concurrently: "^9.1.2", + typescript: "^5.9.2", + vite: "^6.3.5", + "vite-plugin-singlefile": "^2.0.3", + }, + }; + + return JSON.stringify(pkg, null, 2) + "\n"; +} + +export function viteConfig(): string { + return viteConfigTmpl; +} + +export function tsconfigJson(): string { + return ( + JSON.stringify( + { + files: [], + references: [ + { path: "./tsconfig.app.json" }, + { path: "./tsconfig.node.json" }, + ], + }, + null, + 2 + ) + "\n" + ); +} + +export function tsconfigAppJson(): string { + return ( + JSON.stringify( + { + compilerOptions: { + tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + target: "ES2022", + useDefineForClassFields: true, + lib: ["ES2022", "DOM", "DOM.Iterable"], + module: "ESNext", + skipLibCheck: true, + moduleResolution: "bundler", + allowImportingTsExtensions: true, + verbatimModuleSyntax: true, + moduleDetection: "force", + noEmit: true, + jsx: "react-jsx", + strict: true, + noUnusedLocals: true, + noUnusedParameters: true, + noFallthroughCasesInSwitch: true, + baseUrl: ".", + paths: { + "@api/*": ["./api/*"], + }, + }, + include: ["."], + }, + null, + 2 + ) + "\n" + ); +} + +export function tsconfigNodeJson(): string { + return ( + JSON.stringify( + { + compilerOptions: { + tsBuildInfoFile: "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + target: "ES2023", + lib: ["ES2023"], + module: "ESNext", + skipLibCheck: true, + moduleResolution: "bundler", + allowImportingTsExtensions: true, + verbatimModuleSyntax: true, + moduleDetection: "force", + noEmit: true, + strict: true, + noUnusedLocals: true, + noUnusedParameters: true, + noFallthroughCasesInSwitch: true, + }, + include: ["vite.config.ts"], + }, + null, + 2 + ) + "\n" + ); +} + +// ── Per-UI files ─────────────────────────────────────── + +export function indexCss(): string { + return indexCssTmpl; +} + +export function indexHtml(ui: UiEntryWithPackage): string { + return render(indexHtmlTmpl, { + title: ui.title, + }); +} + +export function mainTsx(ui: UiEntryWithPackage): string { + return render(mainTsxTmpl, { + appName: appName(ui), + }); +} + +export function appTsx(ui: UiEntryWithPackage): string { + const pkgPath = ui.package.replace(/\./g, "/"); + + return render(appTsxTmpl, { + appName: appName(ui), + hookName: `use${ui.stateName}`, + importPath: `@api/${pkgPath}/${ui.protoBase}_rbt_react`, + titleLower: ui.title.toLowerCase(), + }); +} + +export function appModuleCss(): string { + return appModuleCssTmpl; +} diff --git a/reboot/create-ui/src/tmpl.d.ts b/reboot/create-ui/src/tmpl.d.ts new file mode 100644 index 00000000..d784eaba --- /dev/null +++ b/reboot/create-ui/src/tmpl.d.ts @@ -0,0 +1,4 @@ +declare module "*.tmpl" { + const content: string; + export default content; +} diff --git a/reboot/create-ui/templates/App.module.css.tmpl b/reboot/create-ui/templates/App.module.css.tmpl new file mode 100644 index 00000000..eaccd86e --- /dev/null +++ b/reboot/create-ui/templates/App.module.css.tmpl @@ -0,0 +1,32 @@ +.container { + background: var(--color-bg); + color: var(--color-text); + font-family: var(--font-mono); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px 20px; +} + +.label { + color: var(--color-text-muted); + font-size: 14px; + margin-bottom: 8px; +} + +.data { + background: var(--color-bg-dark); + border: 1px solid var(--color-border); + border-radius: 4px; + padding: 16px; + font-size: 12px; + max-width: 400px; + overflow-x: auto; + white-space: pre-wrap; + word-break: break-all; +} + +.loading { + color: var(--color-text-muted); +} diff --git a/reboot/create-ui/templates/App.tsx.tmpl b/reboot/create-ui/templates/App.tsx.tmpl new file mode 100644 index 00000000..e15bee0c --- /dev/null +++ b/reboot/create-ui/templates/App.tsx.tmpl @@ -0,0 +1,26 @@ +import { useState, type FC } from "react"; +import { {{hookName}} } from "{{importPath}}"; +import css from "./App.module.css"; + +export const {{appName}}: FC = () => { + const [isPending, setIsPending] = useState(false); + const state = {{hookName}}(); + const { response, isLoading } = state.useGet(); + + if (isLoading && response === undefined) { + return ( +
+
loading...
+
+ ); + } + + return ( +
+
{{titleLower}}
+
+        {JSON.stringify(response, null, 2)}
+      
+
+ ); +}; diff --git a/reboot/create-ui/templates/index.css.tmpl b/reboot/create-ui/templates/index.css.tmpl new file mode 100644 index 00000000..e1458404 --- /dev/null +++ b/reboot/create-ui/templates/index.css.tmpl @@ -0,0 +1,41 @@ +:root { + --color-bg: #1a1a2e; + --color-bg-dark: #0f0f1a; + --color-border: #2d2d4a; + --color-text: #e0e0e0; + --color-text-muted: #888899; + --color-green: #4ade80; + --color-blue: #60a5fa; + --color-yellow: #fbbf24; + --color-pink: #f472b6; + --color-purple: #a78bfa; + --color-orange: #fb923c; + --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, + monospace; +} + +[data-theme="light"] { + --color-bg: #f8f9fa; + --color-bg-dark: #e9ecef; + --color-border: #dee2e6; + --color-text: #212529; + --color-text-muted: #6c757d; + --color-green: #16a34a; + --color-blue: #2563eb; + --color-yellow: #ca8a04; + --color-pink: #db2777; + --color-purple: #7c3aed; + --color-orange: #ea580c; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: var(--font-mono); + background: var(--color-bg); + color: var(--color-text); +} diff --git a/reboot/create-ui/templates/index.html.tmpl b/reboot/create-ui/templates/index.html.tmpl new file mode 100644 index 00000000..61703b57 --- /dev/null +++ b/reboot/create-ui/templates/index.html.tmpl @@ -0,0 +1,12 @@ + + + + + + {{title}} + + +
+ + + diff --git a/reboot/create-ui/templates/main.tsx.tmpl b/reboot/create-ui/templates/main.tsx.tmpl new file mode 100644 index 00000000..f753696a --- /dev/null +++ b/reboot/create-ui/templates/main.tsx.tmpl @@ -0,0 +1,13 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { RebootClientProvider } from "@reboot-dev/reboot-react"; +import { {{appName}} } from "./App"; +import "./index.css"; + +createRoot(document.getElementById("root")!).render( + + + <{{appName}} /> + + +); diff --git a/reboot/create-ui/templates/vite.config.ts.tmpl b/reboot/create-ui/templates/vite.config.ts.tmpl new file mode 100644 index 00000000..b300a4a2 --- /dev/null +++ b/reboot/create-ui/templates/vite.config.ts.tmpl @@ -0,0 +1,88 @@ +// Vite configuration for Reboot UIs. +import fs from "fs"; +import path from "path"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; +import { viteSingleFile } from "vite-plugin-singlefile"; + +// Auto-discover UIs from ui/ directory. +const uiDir = path.resolve(__dirname, "ui"); +const uis: Record = + Object.fromEntries( + fs + .readdirSync(uiDir) + .filter((d) => fs.existsSync(path.join(uiDir, d, "index.html"))) + .map((name) => [ + name, + { input: `ui/${name}/index.html`, output: `${name}.html` }, + ]) + ); + +export default defineConfig(({ command, mode }) => { + // Path alias for API imports (@api/... -> ./api/...). + const resolve = { + alias: { + "@api": path.resolve(__dirname, "./api"), + }, + dedupe: ["react", "react-dom", "zod"], + }; + + // Dev server configuration. + // + // UIs use a double iframe architecture: + // MCP Host -> srcdoc (origin=null) -> iframe (origin=localhost:9991) + // + // The inner iframe loads from Envoy ("/__/web/**"), which proxies + // to Vite. Because the inner iframe has a real origin, Vite's URLs + // work normally. `base: "/__/web/"` ensures all paths route through + // Envoy. + // + // Hot Module Replacement works automatically: Vite's client connects + // to the page's origin, and Envoy proxies WebSocket upgrades to + // Vite. This also works with tunnels (ngrok) since the tunnel + // points to Envoy. + if (command === "serve") { + const port = parseInt(process.env.RBT_VITE_PORT || "4444", 10); + + return { + plugins: [react()], + root: ".", + resolve, + base: "/__/web/", + server: { + port, + strictPort: true, + // Listen on all interfaces since requests come through + // Envoy (and tunnels). + host: true, + allowedHosts: true, + }, + }; + } + + // Build mode: `vite build --mode ` + const ui = uis[mode]; + if (!ui) { + const valid = Object.keys(uis).join(", "); + throw new Error(`Unknown UI: ${mode}. Use --mode with: ${valid}`); + } + + return { + plugins: [react(), viteSingleFile()], + build: { + outDir: "dist", + emptyOutDir: false, + assetsInlineLimit: 100000000, + cssCodeSplit: false, + rollupOptions: { + input: ui.input, + output: { + inlineDynamicImports: true, + entryFileNames: ui.output.replace(".html", ".js"), + assetFileNames: ui.output.replace(".html", ".[ext]"), + }, + }, + }, + resolve, + }; +}); diff --git a/reboot/create-ui/tsconfig.json b/reboot/create-ui/tsconfig.json new file mode 100644 index 00000000..e2f17836 --- /dev/null +++ b/reboot/create-ui/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/reboot/demos/fig/.rbtrc b/reboot/demos/fig/.rbtrc index e846403c..8c144196 100644 --- a/reboot/demos/fig/.rbtrc +++ b/reboot/demos/fig/.rbtrc @@ -5,10 +5,10 @@ generate --nodejs=api generate --react=web/src/api # Save state between chaos restarts. -dev run --name=fig +dev run --application-name=fig dev run --watch=backend/src/**/*.ts dev run --nodejs dev run --application=backend/src/main.ts -dev expunge --name=fig +dev expunge --application-name=fig diff --git a/reboot/demos/fig/package-lock.json b/reboot/demos/fig/package-lock.json index eb96e73e..181e7f16 100644 --- a/reboot/demos/fig/package-lock.json +++ b/reboot/demos/fig/package-lock.json @@ -10,11 +10,11 @@ "dependencies": { "@bufbuild/protobuf": "1.10.1", "@radix-ui/react-icons": "^1.3.0", - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-std": "0.46.0", - "@reboot-dev/reboot-std-api": "0.46.0", - "@reboot-dev/reboot-std-react": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-std": "1.0.3", + "@reboot-dev/reboot-std-api": "1.0.3", + "@reboot-dev/reboot-std-react": "1.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-draggable": "^4.4.6", @@ -1027,9 +1027,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.12", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.12.tgz", - "integrity": "sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -1185,15 +1185,18 @@ } }, "node_modules/@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "license": "MIT", "workspaces": [ "examples/*" ], + "engines": { + "node": ">=20" + }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" @@ -1208,9 +1211,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -1248,9 +1251,9 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1353,15 +1356,15 @@ } }, "node_modules/@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -1390,9 +1393,9 @@ } }, "node_modules/@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0", @@ -1416,15 +1419,15 @@ } }, "node_modules/@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", "license": "Apache-2.0", "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -1452,45 +1455,45 @@ } }, "node_modules/@reboot-dev/reboot-std": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std/-/reboot-std-0.46.0.tgz", - "integrity": "sha512-Sd915ak5YKuLcEteA/ULzJsehiDp3/TSrPHXIw1Osza9Yqakewntv47QsLgTnqCBxhpvQ49WbTqK6iEJQ1Sgqw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std/-/reboot-std-1.0.3.tgz", + "integrity": "sha512-+N6yWU6uVXnJikK8ECh3NuiGo47zfP8ThE34SJCyXzSSJQS7suf9sS2yLCNaWbSKt7vTOrx9clAF9JjEnhoF7Q==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-std-api": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-std-api": "1.0.3", "@scarf/scarf": "1.4.0" } }, "node_modules/@reboot-dev/reboot-std-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-api/-/reboot-std-api-0.46.0.tgz", - "integrity": "sha512-SPt7RsLdJoGxxrf/896yC7/lqBNqVmAPL2BFpZbyCV+Bb205VAodm7ERylhvaFEU7PgucKOrjR7WXXSTeXDo6w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-api/-/reboot-std-api-1.0.3.tgz", + "integrity": "sha512-k6aRTEuL40ki4FN51A7UdkvGK2bAOJ5npStWkL3tpLhIwtmcmdtbNZZeYEdVgPCLEgReIep9bd61U6ObnNzXtA==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0" } }, "node_modules/@reboot-dev/reboot-std-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-react/-/reboot-std-react-0.46.0.tgz", - "integrity": "sha512-QVQj7DsjsbdXAPmIRN3xrjnabYfB1nIk00FQxEs31sXQwdP1Mcr0QddwzyhrtoX84t5+HNV5YZWz2vP+PLcQQg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-react/-/reboot-std-react-1.0.3.tgz", + "integrity": "sha512-EcbLjY5BvaGa5XIyyVRN0z/wUSHbR+rclcMqI27GddGlk7j9yz4Bn7FI40s7QZB9shuGdiVjp//S6GB4lZ/yvw==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-react": "0.46.0", - "@reboot-dev/reboot-std-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", + "@reboot-dev/reboot-std-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0" } }, "node_modules/@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -2547,9 +2550,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -3557,9 +3560,9 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -3614,9 +3617,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz", - "integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "license": "MIT", "dependencies": { "ip-address": "10.1.0" @@ -4083,9 +4086,9 @@ } }, "node_modules/hono": { - "version": "4.12.9", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz", - "integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==", + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -4342,9 +4345,9 @@ } }, "node_modules/jose": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", - "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -7767,9 +7770,9 @@ "dev": true }, "@hono/node-server": { - "version": "1.19.12", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.12.tgz", - "integrity": "sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "requires": {} }, "@humanwhocodes/config-array": { @@ -7884,15 +7887,15 @@ } }, "@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "requires": {} }, "@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "requires": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -7914,9 +7917,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7997,13 +8000,13 @@ "requires": {} }, "@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "requires": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -8191,9 +8194,9 @@ } }, "@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "requires": { "@scarf/scarf": "1.4.0", "typescript": "5.4.5", @@ -8208,14 +8211,14 @@ } }, "@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", - "requires": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", + "requires": { + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -8232,41 +8235,41 @@ } }, "@reboot-dev/reboot-std": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std/-/reboot-std-0.46.0.tgz", - "integrity": "sha512-Sd915ak5YKuLcEteA/ULzJsehiDp3/TSrPHXIw1Osza9Yqakewntv47QsLgTnqCBxhpvQ49WbTqK6iEJQ1Sgqw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std/-/reboot-std-1.0.3.tgz", + "integrity": "sha512-+N6yWU6uVXnJikK8ECh3NuiGo47zfP8ThE34SJCyXzSSJQS7suf9sS2yLCNaWbSKt7vTOrx9clAF9JjEnhoF7Q==", "requires": { - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-std-api": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-std-api": "1.0.3", "@scarf/scarf": "1.4.0" } }, "@reboot-dev/reboot-std-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-api/-/reboot-std-api-0.46.0.tgz", - "integrity": "sha512-SPt7RsLdJoGxxrf/896yC7/lqBNqVmAPL2BFpZbyCV+Bb205VAodm7ERylhvaFEU7PgucKOrjR7WXXSTeXDo6w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-api/-/reboot-std-api-1.0.3.tgz", + "integrity": "sha512-k6aRTEuL40ki4FN51A7UdkvGK2bAOJ5npStWkL3tpLhIwtmcmdtbNZZeYEdVgPCLEgReIep9bd61U6ObnNzXtA==", "requires": { "@scarf/scarf": "1.4.0" } }, "@reboot-dev/reboot-std-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-react/-/reboot-std-react-0.46.0.tgz", - "integrity": "sha512-QVQj7DsjsbdXAPmIRN3xrjnabYfB1nIk00FQxEs31sXQwdP1Mcr0QddwzyhrtoX84t5+HNV5YZWz2vP+PLcQQg==", - "requires": { - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-react": "0.46.0", - "@reboot-dev/reboot-std-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-react/-/reboot-std-react-1.0.3.tgz", + "integrity": "sha512-EcbLjY5BvaGa5XIyyVRN0z/wUSHbR+rclcMqI27GddGlk7j9yz4Bn7FI40s7QZB9shuGdiVjp//S6GB4lZ/yvw==", + "requires": { + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", + "@reboot-dev/reboot-std-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0" } }, "@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "requires": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -8701,9 +8704,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -9409,9 +9412,9 @@ } }, "eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==" }, "exponential-backoff": { "version": "3.1.1", @@ -9454,9 +9457,9 @@ } }, "express-rate-limit": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz", - "integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "requires": { "ip-address": "10.1.0" }, @@ -9789,9 +9792,9 @@ } }, "hono": { - "version": "4.12.9", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz", - "integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==" + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==" }, "http-cache-semantics": { "version": "4.1.1", @@ -9973,9 +9976,9 @@ "dev": true }, "jose": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", - "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==" + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==" }, "js-sha1": { "version": "0.7.0", diff --git a/reboot/demos/fig/package.json b/reboot/demos/fig/package.json index 643d456a..a9ed6116 100644 --- a/reboot/demos/fig/package.json +++ b/reboot/demos/fig/package.json @@ -11,11 +11,11 @@ "dependencies": { "@bufbuild/protobuf": "1.10.1", "@radix-ui/react-icons": "^1.3.0", - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-std": "0.46.0", - "@reboot-dev/reboot-std-api": "0.46.0", - "@reboot-dev/reboot-std-react": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-std": "1.0.3", + "@reboot-dev/reboot-std-api": "1.0.3", + "@reboot-dev/reboot-std-react": "1.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-draggable": "^4.4.6", diff --git a/reboot/examples/README.j2.md b/reboot/examples/README.j2.md index 06484392..66a5f9b6 100644 --- a/reboot/examples/README.j2.md +++ b/reboot/examples/README.j2.md @@ -9,13 +9,13 @@ For the impatient: ### Overview {{ overview }} -The [Reboot '.proto' definitions](https://docs.reboot.dev/develop/define/overview/#code-generation) +The [Reboot '.proto' definitions](https://docs.reboot.dev/learn_more/define/overview#code-generation) can be found in the `api/` directory, grouped into subdirectories by proto package, while backend specific code can be found in {{ backend_locations }}{% if has_frontend %} and front end specific code in `web/`{% endif %} {% if has_non_react_frontend %} and non-React front end in `reboot-non-react-web/`{% endif %}. -_For more information on all of the Reboot examples, please [see the docs](https://docs.reboot.dev/get_started/examples)._ +_For more information on all of the Reboot examples, please [see the docs](https://docs.reboot.dev/full_stack_apps/examples)._ ## Prepare an environment by... diff --git a/reboot/examples/agent-wiki/.devcontainer/devcontainer.json b/reboot/examples/agent-wiki/.devcontainer/devcontainer.json new file mode 100644 index 00000000..88b10cc0 --- /dev/null +++ b/reboot/examples/agent-wiki/.devcontainer/devcontainer.json @@ -0,0 +1,35 @@ +// For format details, see https://aka.ms/devcontainer.json. +{ + "name": "Reboot Devcontainer", + "image": "ghcr.io/reboot-dev/reboot-devcontainer", + "remoteUser": "dev", + + "customizations": { + "vscode": { + // The following VS Code extensions are nice to have when developing on + // this Reboot application. + "extensions": [ + // Python support. + "ms-python.python", + // Static type checking for Python. + "ms-python.mypy-type-checker", + // YAPF code formatter for Python. + "eeyore.yapf", + // Automatic import sorting for Python. + "ms-python.isort", + // Protocol buffer support. + "zxh404.vscode-proto3", + // Prettier code formatter. + "esbenp.prettier-vscode" + ] + } + }, + + // Ports from the devcontainer made available on your local machine. + "forwardPorts": [ + // The HTTP port exposed by default in examples. + 3000, + // The gRPC backend port exposed by default in examples. + 9991 + ] +} diff --git a/reboot/examples/agent-wiki/.dockerignore b/reboot/examples/agent-wiki/.dockerignore new file mode 100644 index 00000000..2d96968d --- /dev/null +++ b/reboot/examples/agent-wiki/.dockerignore @@ -0,0 +1,39 @@ +# Python runtime caches and venv — rebuilt inside the image. +.venv/ +__pycache__/ +*.py[cod] +.mypy_cache/ +.pytest_cache/ + +# Reboot runtime state (dev-only). +.rbt/ + +# Generated code — regenerated inside the image by `rbt +# generate`. +backend/api/ +web/api/ + +# Node sources / deps / caches — we only copy `web/dist/` +# into the image. +web/node_modules/ +web/ui/ +web/src/ +web/package.json +web/package-lock.json +web/tsconfig*.json +web/vite.config.ts +web/index.css + +# VCS. +.git/ +.gitignore + +# Editor / OS. +.vscode/ +.idea/ +*.swp +.DS_Store + +# Local-only files. +mcp_servers.json +README.md diff --git a/reboot/examples/agent-wiki/.github/workflows/test.yml b/reboot/examples/agent-wiki/.github/workflows/test.yml new file mode 100644 index 00000000..007291c8 --- /dev/null +++ b/reboot/examples/agent-wiki/.github/workflows/test.yml @@ -0,0 +1,47 @@ +name: Test + +on: + push: + # Run CI (only) on our main branch to show that it is always in a green + # state. Don't run it on other branches; if a developer cares about one + # of the non-main branches they'll run tests manually or open a PR. + branches: + - "main" + pull_request: + # Run CI on pull requests. Passing checks will be required to merge. + branches: + - "**" + merge_group: + # Required if we want to use GitHub's Merge Queue feature. See: + # https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue#triggering-merge-group-checks-with-github-actions + +concurrency: + # Have at most one of these workflows running per branch, cancelling older + # runs that haven't completed yet when they become obsolete. + # + # When pushing new commits to a PR, each one of those commits triggers a new + # workflow run that would normally run to completion, even when subsequent + # pushes to the PR make their result obsolete. This consumes resources for no + # benefit. We override that default behavior to save resources, cancelling any + # older still-running workflows when a new workflow starts + # + # See documentation about the `github` context variables here: + # https://docs.github.com/en/actions/learn-github-actions/contexts#github-context + group: ${{ github.workflow }}-${{ github.ref }} + # Do not cancel runs on the main branch. On the main branch we want every + # commit to be tested. + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + backend-test: + name: Backend test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: pytest (in Dev Container) + uses: devcontainers/ci@v0.3 + with: + runCmd: | + .tests/test.sh diff --git a/reboot/examples/agent-wiki/.gitignore b/reboot/examples/agent-wiki/.gitignore new file mode 100644 index 00000000..ffb85d3a --- /dev/null +++ b/reboot/examples/agent-wiki/.gitignore @@ -0,0 +1,33 @@ +# Python virtual environment. +.venv/ + +# Byte-compiled files. +__pycache__/ +*.py[cod] +*$py.class + +# Generated API code (from `rbt generate`). +backend/api/ +web/api/ + +# Reboot runtime state. +.rbt/ + +# Distribution / packaging. +*.egg-info/ + +# mypy / pytest caches. +.mypy_cache/ +.pytest_cache/ + +# Node / web build artifacts. +web/node_modules/ +web/dist/ + +# IDE / editor. +.idea/ +.vscode/ +*.swp + +# OS. +.DS_Store diff --git a/reboot/examples/agent-wiki/.mergequeue/config.yml b/reboot/examples/agent-wiki/.mergequeue/config.yml new file mode 100644 index 00000000..6c4cfe2e --- /dev/null +++ b/reboot/examples/agent-wiki/.mergequeue/config.yml @@ -0,0 +1,32 @@ +version: 1.0.0 +merge_rules: + publish_status_check: true + labels: + trigger: mergequeue-ready + skip_line: "" + merge_failed: "" + skip_delete_branch: "" + update_latest: true + delete_branch: false + use_rebase: true + enable_comments: true + ci_timeout_mins: 0 + preconditions: + number_of_approvals: 1 + use_github_mergeability: true + conversation_resolution_required: true + merge_mode: + type: default + parallel_mode: null + auto_update: + enabled: false + label: "" + max_runs_for_update: 0 + merge_commit: + use_title_and_body: false + merge_strategy: + name: squash + override_labels: + squash: "" + merge: "" + rebase: mergequeue-rebase diff --git a/reboot/examples/agent-wiki/.python-version b/reboot/examples/agent-wiki/.python-version new file mode 100644 index 00000000..9919bf8c --- /dev/null +++ b/reboot/examples/agent-wiki/.python-version @@ -0,0 +1 @@ +3.10.13 diff --git a/reboot/examples/agent-wiki/.rbtrc b/reboot/examples/agent-wiki/.rbtrc new file mode 100644 index 00000000..205eb8fa --- /dev/null +++ b/reboot/examples/agent-wiki/.rbtrc @@ -0,0 +1,49 @@ +# Find API definitions in 'api/'. +generate api/ + +# Tell `rbt generate` where to put generated files. +generate --python=backend/api/ + +# Generate React bindings for web apps (into "web/api/"). +generate --react=web/api +generate --react-extensions + +# Watch if any source files are modified. +dev run --watch=backend/**/*.py + +# Tell `rbt` that this is a Python application. +dev run --python + +# Save state between restarts. +dev run --application-name=agent-wiki + +# Run the application! +dev run --application=backend/src/main.py + +# Default to HMR mode when no --config is specified. +dev run --default-config=hmr + +# Hot Module Replacement (HMR): Vite dev server proxied through Envoy. +# Run Vite in a separate terminal: cd web && npm run dev +# Envoy routes "/__/web/**" to Vite for HMR support. +dev run:hmr --mcp-frontend-host=http://localhost:4444 + +# Dist mode: serve pre-built artifacts from "web/dist/" (no Vite HMR). +# Usage: uv run rbt dev run --config=dist +# Requires: cd web && npm run build +dev run:dist --mcp-frontend-host="" + +# When expunging, expunge that state we've saved. +dev expunge --application-name=agent-wiki + +# Production (`rbt serve run`) settings, used by the Dockerfile +# when deploying to the Reboot Cloud. In production `web/dist/` +# is served from disk, so no `--mcp-frontend-host` is needed +# (that flag is dev-only anyway). +serve run --python +serve run --application-name=agent-wiki +serve run --application=backend/src/main.py + +# Leave TLS termination to the external load balancer; expose +# a non-SSL port to that load balancer. +serve run --tls=external diff --git a/reboot/examples/agent-wiki/.tests/test.sh b/reboot/examples/agent-wiki/.tests/test.sh new file mode 100755 index 00000000..c3102f0a --- /dev/null +++ b/reboot/examples/agent-wiki/.tests/test.sh @@ -0,0 +1,99 @@ +#!/bin/bash + +set -e # Exit if a command exits with an error. +set -x # Echo executed commands to help debug failures. + +# MacOS tests can fail due to a race in `protoc` writing files to +# disk, so now we check only occurrences of the expected lines in +# the output. See https://github.com/reboot-dev/mono/issues/3433 +check_lines_in_file() { + local expected="$1" + local actual="$2" + + while IFS= read -r line; do + if ! grep -Fxq "$line" "$actual"; then + echo "Line $line is missing in the actual output." + exit 1 + fi + done < "$expected" +} + +# Use the published Reboot pip package by default, but allow the +# test system to override it with a different value. +if [ -n "$REBOOT_WHL_FILE" ]; then + # Install the `reboot` package from the specified path + # explicitly, overwriting the version from `pyproject.toml`. + rye remove --no-sync reboot + rye remove --no-sync --dev reboot + rye add --dev reboot --absolute --path="${SANDBOX_ROOT}$REBOOT_WHL_FILE" +fi + +# Force a fresh virtualenv. A pre-existing `.venv/` (e.g., +# carried over from a pre-baked image, or copied between +# containers/host paths during the dev-container test) will +# have absolute shebangs in its console scripts (`rbt`, +# `pytest`) pointing at the path where it was originally +# created, which produces "bad interpreter: No such file or +# directory" when those scripts are executed from a different +# location. `rye sync` only regenerates entry-point scripts +# for packages it reinstalls, so it can't repair an existing +# venv whose shebangs are stale. Nuking and re-syncing here +# guarantees the venv lives at the current path. +rm -rf .venv +rye sync --no-lock + +# Don't `source .venv/bin/activate`: that script bakes in the +# venv's original creation path on its first line, which is +# wrong when the venv has been relocated (e.g., a pre-baked +# image bind-mounted at a different path in CI). The +# console-script wrappers (`rbt`, `pytest`) also have absolute +# shebangs that point at the stale path. Set `PATH` and +# `VIRTUAL_ENV` to the real location ourselves, and invoke the +# tools via `python -m`: the `python` symlink resolves the +# venv from its own location through `pyvenv.cfg`, so it +# survives relocation, and `-m` bypasses the broken script +# shebangs. +VENV_BIN="$(pwd)/.venv/bin" +export PATH="$VENV_BIN:$PATH" +export VIRTUAL_ENV="$(pwd)/.venv" +PYTHON="$VENV_BIN/python" + +# When running in a Bazel test, our `.rbtrc` file ends up in a +# very deep directory structure, which can result in "path too +# long" errors from RocksDB. Explicitly specify a shorter path. +RBT_FLAGS="--state-directory=$(mktemp -d)" + +"$PYTHON" -m reboot.cli.rbt_main $RBT_FLAGS generate + +"$PYTHON" -m pytest backend/ + +if [ -n "$EXPECTED_RBT_DEV_OUTPUT_FILE" ]; then + actual_output_file=$(mktemp) + + # `--config=dist` overrides `.rbtrc`'s default `hmr` config, + # whose `--mcp-frontend-host=http://localhost:4444` would + # have Envoy proxy `/__/web/**` to a Vite dev server. There + # is no Vite running in CI, and on the macOS executable-Envoy + # path that proxy target makes cluster init hang + # indefinitely, so `--terminate-after-health-check` never + # fires. The `dist` config sets `--mcp-frontend-host=""`, + # which skips the proxy entirely. `web/dist/` doesn't need + # to actually exist; the health check only probes gRPC and + # Envoy listeners. + # + # The librarian agent fails fast if `ANTHROPIC_API_KEY` is + # unset, so we provide a dummy value. No real Anthropic calls + # are made: the health check terminates the process before any + # transcript ingestion happens. + ANTHROPIC_API_KEY="dummy-for-health-check" \ + "$PYTHON" -m reboot.cli.rbt_main $RBT_FLAGS dev run \ + --config=dist \ + --terminate-after-health-check \ + > "$actual_output_file" + + check_lines_in_file \ + "${SANDBOX_ROOT}$EXPECTED_RBT_DEV_OUTPUT_FILE" \ + "$actual_output_file" + + rm "$actual_output_file" +fi diff --git a/reboot/examples/agent-wiki/BUILD.bazel b/reboot/examples/agent-wiki/BUILD.bazel new file mode 100644 index 00000000..3afcc403 --- /dev/null +++ b/reboot/examples/agent-wiki/BUILD.bazel @@ -0,0 +1,24 @@ +# This `BUILD.bazel` file allows a `bazel` system (configured outside of +# this repository) to use code in this directory and its subdirectories. + +filegroup( + name = "everything", + srcs = glob( + ["**/*"], + exclude = [ + # Files that may be created by activity in this directory, + # such as manually following the steps of the `README.md` + # file, but which are not part of the "source code" of this + # repository. + ".venv/**/*", + ".rbt/**/*", + ".pytest_cache/**/*", + ".mypy_cache/**/*", + "backend/api/**/*", + "web/api/**/*", + "web/node_modules/**/*", + "web/dist/**/*", + ], + ), + visibility = ["//visibility:public"], +) diff --git a/reboot/examples/agent-wiki/Dockerfile b/reboot/examples/agent-wiki/Dockerfile new file mode 100644 index 00000000..3b42d491 --- /dev/null +++ b/reboot/examples/agent-wiki/Dockerfile @@ -0,0 +1,45 @@ +# Dockerfile for deploying agent-wiki to Reboot Cloud. +# +# Prerequisite: run `cd web && npm install && npm run build` +# locally before `docker build` so that `web/dist/` contains the +# bundled UIs. This image copies that prebuilt bundle rather +# than installing Node and rebuilding it here. +FROM ghcr.io/reboot-dev/reboot-base:1.0.3 + +WORKDIR /app + +# Install Python dependencies from the rye-generated lockfile. +# `pip` accepts the `requirements.lock` format directly. This +# layer is cached until the lockfile changes, so app-code edits +# don't trigger a re-install of the dependency tree. The base +# image already includes Reboot itself; the lockfile pins it to +# the matching version. +COPY requirements.lock requirements.txt +RUN pip install -r requirements.txt + +# Copy the API definition and `.rbtrc`, then generate Reboot +# code. Separate layer so regeneration only reruns when the +# API changes. +COPY api/ api/ +COPY .rbtrc .rbtrc +RUN rbt generate + +# Copy the backend source. +COPY backend/src/ backend/src/ + +# Copy the prebuilt web bundle. +COPY web/dist/ web/dist/ + +# Make the Pydantic API definitions in `api/` and the generated +# Reboot bindings in `backend/api/` both importable. They share +# the `agent_wiki.v1` namespace package, so both directories +# must be on `PYTHONPATH`. `rbt dev run --python` does this +# automatically; `rbt serve run --python` currently only adds +# the generated-code directory, so we set the path here. +ENV PYTHONPATH=/app/api:/app/backend/api + +# Start the Reboot production runtime. `rbt serve run` reads +# its flags (`--python`, `--application=...`, `--name=...`, +# `--tls=external`) from `.rbtrc`. `--port` is picked up from +# the `PORT` env var set by Reboot Cloud. +CMD ["rbt", "serve", "run"] diff --git a/reboot/examples/agent-wiki/README.md b/reboot/examples/agent-wiki/README.md new file mode 100644 index 00000000..a65282e7 --- /dev/null +++ b/reboot/examples/agent-wiki/README.md @@ -0,0 +1,204 @@ +# Agent Wiki + +An AI Chat App built with Reboot that provides a shared +knowledge base humans and AIs can both read from and write to. +Users hand in raw conversation `Transcript`s; a background +"librarian" agent progressively distills those transcripts into +a small, well-organized set of markdown `Page`s linked from the +`Wiki`'s own markdown body, which serves as a living table of +contents. Humans browse the result in embedded React UIs. + +Inspired by Andrej Karpathy's "LLM Knowledge Bases" tweet: +https://x.com/karpathy/status/2039805659525644595 + +## State model + +- **`Wiki`** — a knowledge base. Holds a `name`, a + `description`, and a `content` field that is a single + markdown blob. The table of contents, cross-references, and + any other structure all live inside that markdown, not in + separate fields. Links to other state instances are embedded + as URIs of the form `:`, for example + `Page:abc123` or `Transcript:xyz789`; a reader follows such a + link by calling the referenced type's `get` method on the + embedded state ID. The Wiki also holds a `transcripts` map + from transcript ID to a bool flag indicating whether the + librarian has ingested that transcript yet. +- **`Page`** — a unit of knowledge. Holds a `title` and a + markdown `content` body. Pages are referenced from the wiki + (and from one another) via `Page:` URIs embedded in + markdown. +- **`Transcript`** — a raw conversation, stored as a list of + `{role, content}` messages. A transcript belongs to exactly + one wiki. +- **`User`** — per-user state mapping user-given wiki names + (e.g., `"team knowledge base"`) to opaque wiki state IDs, so + wikis can be looked up by name later. + +The cross-state operations are: + +- `User.create_wiki` — creates a `Wiki` and records its state + ID under the given name on the user. Also schedules the + wiki's `ingest` workflow (see below). +- `Wiki.add_transcript` — creates a `Transcript` with the + given messages and records it on the wiki as + not-yet-ingested, which wakes the librarian. + +Both are `Transaction`s so their state changes land atomically. + +## The librarian + +Every `Wiki` has a long-running `ingest` workflow scheduled at +creation time. That workflow is the **librarian**: a Pydantic +AI agent (see `backend/src/servicers/wiki.py`) whose job is to +watch the wiki's `transcripts` map for pending entries and, for +each one, fold its material into the wiki and its pages, then +mark it ingested. + +The librarian is deliberately biased towards aggregation. Given +a new transcript, it prefers **renaming and broadening an +existing page** over creating a new one — so a page titled +"History of the Steam Engine" may grow into "History of +Inventions" and later into "History of Things" as more +transcripts arrive. The goal is a small, high-signal set of +pages, not one page per transcript. + +The user-facing write path is therefore: call +`Wiki.add_transcript` with the conversation you want captured, +then let the librarian do the rest asynchronously. +`Wiki.update`, `Page.update`, and `Transcript.update` are +exposed too, but are intended mostly for the librarian (and +for manual fix-ups). + +## Quick start + +```bash +# Install Python dependencies and create the virtualenv. +rye sync +source .venv/bin/activate + +# Install web dependencies. +cd web && npm install && cd .. + +# Generate API code (Python + React bindings). +rbt generate + +# Build the React UIs. +cd web && npm run build && cd .. +``` + +The librarian runs on Anthropic's Claude via Pydantic AI, so +the backend needs an Anthropic API key. For local development, +export it in your shell: + +```bash +export ANTHROPIC_API_KEY=... +``` + +When deployed to Reboot Cloud, store the key as a Reboot +secret instead: + +```bash +rbt cloud secret set --application-name=agent-wiki ANTHROPIC_API_KEY=sk-ant-... +``` + +Cloud injects each secret into the application's environment +under its given name, so naming the secret `ANTHROPIC_API_KEY` +is what the Anthropic SDK reads directly — the same code works +in both places. + +Then run the app (each command in its own terminal, from the +project directory, with `.venv` activated): + +```bash +# Terminal 1: start the Reboot backend. +rbt dev run + +# Terminal 2: start the Vite dev server for Hot Module Replacement. +cd web && npm run dev +``` + +State persists between restarts under the name `agent-wiki` +(configured in `.rbtrc`). To wipe it: + +```bash +rbt dev expunge --application-name=agent-wiki +``` + +## Running the tests + +The backend has an in-process test suite that exercises the +servicers and the `Wiki.ingest` librarian workflow without +making any real Anthropic calls (the LLM is replaced by a +scripted Pydantic AI `FunctionModel`). + +```bash +rye sync +source .venv/bin/activate +pytest backend/ +``` + +## Testing with MCPJam Inspector + +`mcp_servers.json` is pre-configured. In another terminal: + +```bash +npx @mcpjam/inspector@v2.4.0 --config mcp_servers.json --server agent-wiki +``` + +Try these prompts to exercise each capability. The librarian +works asynchronously, so after an `add_transcript` call watch +the backend log for `librarian[...] tool call ...` lines to see +it read the wiki, decide where material belongs, and rewrite +pages. + +1. `Create a wiki called "Team Knowledge Base" about + engineering runbooks.` — exercises `create_wiki`, which + also schedules the wiki's `ingest` workflow. +2. `List all my wikis.` — exercises `list_wikis`. +3. `Attach this conversation to the Team Knowledge Base as a + transcript:` followed by a short back-and-forth about, say, + pushing to main and smoke-testing the deploy — exercises + `add_transcript`. The librarian will then create a page + and edit the wiki's markdown to link to it. +4. `Show me the Team Knowledge Base.` — exercises `show_wiki`; + the rendered markdown is the librarian's table of contents + with `Page:` links. +5. `Read the Team Knowledge Base.` — exercises `Wiki.get` and + returns the raw markdown the UI renders. +6. `Follow the first page link in that wiki and show it to + me.` — exercises `Page.get` (to read) and `show_page` (to + render). +7. `Attach a second transcript about our rollback runbook:` + followed by another conversation — exercises + `add_transcript` again. The librarian should prefer + **broadening the existing page** (e.g., renaming it to + "Deploy and Rollback Process") rather than creating a new + one. +8. `Show me the raw transcript we attached first.` — exercises + `Transcript.get` and `show_transcript`. + +## Project layout + +``` +agent-wiki/ +├── api/agent_wiki/v1/wiki.py # State models + method declarations. +├── backend/ +│ ├── api/ # Generated Python bindings. +│ └── src/ +│ ├── main.py # Application entrypoint. +│ └── servicers/wiki.py # User/Wiki/Page/Transcript servicers +│ # plus the Pydantic AI librarian agent. +└── web/ + ├── api/ # Generated React bindings. + └── ui/ + ├── wiki_view/ # Renders a Wiki's markdown body. + ├── page_view/ # Renders a Page's markdown body. + ├── transcript_view/ # Renders a raw Transcript. + └── _shared/ # Shared UI helpers (markdown, etc.). +``` + +## Learn more + +- [Reboot Documentation](https://docs.reboot.dev) +- [MCP Specification](https://modelcontextprotocol.io) diff --git a/reboot/examples/agent-wiki/api/agent_wiki/v1/wiki.py b/reboot/examples/agent-wiki/api/agent_wiki/v1/wiki.py new file mode 100644 index 00000000..d3c974c7 --- /dev/null +++ b/reboot/examples/agent-wiki/api/agent_wiki/v1/wiki.py @@ -0,0 +1,363 @@ +from reboot.api import ( + API, + UI, + Field, + Methods, + Model, + Reader, + Tool, + Transaction, + Type, + Workflow, + Writer, +) + +# -- User models. -- + + +class UserState(Model): + # Map from the user-given wiki name (e.g., "my wiki" or + # "team foo wiki") to the Wiki's opaque state ID. The + # name is how the user refers to the wiki; the state ID + # is what Reboot needs to address it. + wikis: dict[str, str] = Field(tag=1, default_factory=dict) + + +class UserCreateWikiRequest(Model): + name: str = Field(tag=1) + description: str = Field(tag=2) + + +class UserCreateWikiResponse(Model): + wiki_id: str = Field(tag=1) + + +class WikiSummary(Model): + wiki_id: str = Field(tag=1) + name: str = Field(tag=2) + description: str = Field(tag=3) + + +class UserListWikisResponse(Model): + wikis: list[WikiSummary] = Field(tag=1, default_factory=list) + + +# -- Wiki models. -- + + +class WikiState(Model): + name: str = Field(tag=1, default="") + description: str = Field(tag=2, default="") + # Markdown body of the Wiki. All structure within the + # wiki — table of contents, cross-references, links to + # pages or transcripts — lives inside this markdown, not + # in separate structured fields. Links to other state + # instances are embedded as URIs of the form + # `:`, for example `Page:abc123` + # or `Transcript:xyz789`. To follow a link, call the + # corresponding type with the referenced state ID + # (e.g., `Page.get` on `abc123`). + content: str = Field(tag=4, default="") + # Map from the ID of a Transcript added to this Wiki to + # whether that Transcript has been ingested by the + # librarian workflow (True) or is still pending (False). + # The `ingest` workflow watches this map for pending + # entries and folds each transcript's material into the + # wiki's markdown `content` and into Pages it references. + transcripts: dict[str, bool] = Field(tag=5, default_factory=dict) + + +class WikiCreateRequest(Model): + name: str = Field(tag=1) + description: str = Field(tag=2) + + +class WikiGetResponse(Model): + name: str = Field(tag=1) + description: str = Field(tag=2) + content: str = Field(tag=3) + + +class WikiUpdateRequest(Model): + content: str = Field(tag=1) + + +class TranscriptMessage(Model): + """A single message within a conversation Transcript.""" + # The name of whoever added this message, e.g., "user" + # or "assistant". + role: str = Field(tag=1) + # The text content of the message. + content: str = Field(tag=2) + + +class WikiAddTranscriptRequest(Model): + messages: list[TranscriptMessage] = Field(tag=1, default_factory=list) + + +class WikiAddTranscriptResponse(Model): + transcript_id: str = Field(tag=1) + + +# -- Page models. -- + + +class PageState(Model): + title: str = Field(tag=1, default="") + # Markdown body of the Page. A page may draw material + # from many Transcripts (or summaries of them); merge + # that into this markdown as appropriate. Links to + # other state instances are embedded as URIs of the + # form `:`, for example + # `Page:abc123` or `Transcript:xyz789`. To follow a + # link, call the corresponding type with the referenced + # state ID (e.g., `Page.get` on `abc123`). + content: str = Field(tag=2, default="") + + +class PageCreateRequest(Model): + title: str = Field(tag=1) + content: str = Field(tag=2) + + +class PageGetResponse(Model): + title: str = Field(tag=1) + content: str = Field(tag=2) + + +class PageUpdateRequest(Model): + title: str = Field(tag=1) + content: str = Field(tag=2) + + +# -- Transcript models. -- + + +class TranscriptState(Model): + messages: list[TranscriptMessage] = Field(tag=1, default_factory=list) + + +class TranscriptCreateRequest(Model): + messages: list[TranscriptMessage] = Field(tag=1, default_factory=list) + + +class TranscriptGetResponse(Model): + messages: list[TranscriptMessage] = Field(tag=1, default_factory=list) + + +class TranscriptUpdateRequest(Model): + messages: list[TranscriptMessage] = Field(tag=1, default_factory=list) + + +api = API( + User=Type( + state=UserState, + methods=Methods( + create_wiki=Transaction( + request=UserCreateWikiRequest, + response=UserCreateWikiResponse, + description="Create a new Wiki under the " + "given user-facing `name` (e.g., 'my'" + "'wiki' or 'team foo wiki') with the " + "given description. The name is recorded " + "on the user as a mapping to the Wiki's " + "opaque state ID so the wiki can be looked " + "up by name later. The Wiki's body is a " + "single markdown blob; any table of " + "contents, cross-links, or structure you " + "want lives inside that markdown and can " + "be set later via `update`. Returns a " + "`wiki_id` which is not human-readable but " + "should be passed to future tool calls.", + mcp=Tool(), + ), + list_wikis=Reader( + request=None, + response=UserListWikisResponse, + description="List all wikis owned by this " + "user. Returns `wiki_id`, name, and " + "description for each. Use a `wiki_id` when " + "calling other tools.", + mcp=Tool(), + ), + ), + ), + Wiki=Type( + state=WikiState, + methods=Methods( + show_wiki=UI( + request=None, + path="web/ui/wiki_view", + title="Wiki", + description="Open the Wiki viewer UI to " + "render the Wiki's markdown body.", + ), + create=Writer( + request=WikiCreateRequest, + response=None, + factory=True, + mcp=None, + ), + get=Reader( + request=None, + response=WikiGetResponse, + description="Get this Wiki's name, " + "description, and markdown `content` body. " + "The markdown is the only place structure " + "lives: any table of contents or " + "cross-references are embedded in it. " + "Links to other state instances appear as " + "URIs of the form `:`, " + "e.g., `Page:abc123` or `Transcript:xyz789`. " + "Follow any such link by calling that type " + "with the referenced state ID — for " + "example, call `Page.get` on `abc123` to " + "read that page's markdown.", + mcp=Tool(), + ), + update=Writer( + request=WikiUpdateRequest, + response=None, + description="Replace this Wiki's markdown " + "body with new content. Put the table of " + "contents, cross-references, and any " + "structure directly into the markdown. " + "Link to other state instances (Pages, " + "Transcripts, other Wikis) by writing " + "URIs of the form `:`, " + "e.g., `Page:abc123`. Readers follow those " + "links by calling the named type with the " + "embedded state ID.", + mcp=Tool(), + ), + ingest=Workflow( + request=None, + response=None, + description="Long-running librarian " + "workflow scheduled once per Wiki. It " + "watches the `transcripts` map on the " + "Wiki for entries marked not-yet-ingested " + "and, for each, runs an LLM agent that " + "folds the transcript's material into the " + "Wiki's markdown body and into Pages it " + "references (updating existing pages and " + "creating new ones as needed), then marks " + "the transcript ingested. Not intended to " + "be called directly by users.", + mcp=None, + ), + add_transcript=Transaction( + request=WikiAddTranscriptRequest, + response=WikiAddTranscriptResponse, + description="Add a conversation transcript " + "to this Wiki by creating a new Transcript " + "containing the given `messages` (a list " + "of {role, content} entries capturing the " + "conversation turn by turn) and recording " + "it on the Wiki as not-yet-ingested. Any " + "publicly available links referenced in " + "the conversation — for example URLs to " + "images — should be included verbatim in " + "the message `content` so the librarian " + "can see them. A transcript belongs to " + "exactly one Wiki. The `ingest` workflow " + "running on the Wiki will then fold the " + "transcript's material into the Wiki's " + "markdown `content` and into the Pages it " + "references, and mark the transcript " + "ingested when done. Returns the new " + "`transcript_id`.", + mcp=Tool(), + ), + ), + ), + Page=Type( + state=PageState, + methods=Methods( + show_page=UI( + request=None, + path="web/ui/page_view", + title="Page", + description="Open the Page viewer UI to " + "render the Page's markdown body.", + ), + create=Writer( + request=PageCreateRequest, + response=None, + factory=True, + mcp=None, + ), + get=Reader( + request=None, + response=PageGetResponse, + description="Get this Page's title and " + "markdown `content` body. The markdown is " + "the only place structure lives; outbound " + "links appear inline as URIs of the form " + "`:`, e.g., " + "`Page:abc123` or `Transcript:xyz789`. " + "Follow any such link by calling that type " + "with the referenced state ID — for " + "example, call `Page.get` on `abc123` to " + "read that page's markdown.", + mcp=Tool(), + ), + update=Writer( + request=PageUpdateRequest, + response=None, + description="Replace this page's title and " + "markdown body. Renaming a page is fine — " + "and expected — as its scope broadens to " + "absorb new material. Put any outbound " + "links directly into the markdown as URIs " + "of the form `:`, " + "e.g., `Page:abc123` or " + "`Transcript:xyz789`. Readers follow those " + "links by calling the named type with the " + "embedded state ID.", + mcp=Tool(), + ), + ), + ), + Transcript=Type( + state=TranscriptState, + methods=Methods( + show_transcript=UI( + request=None, + path="web/ui/transcript_view", + title="Transcript", + description="Open the Transcript viewer " + "UI to read the raw conversation " + "transcript.", + ), + create=Writer( + request=TranscriptCreateRequest, + response=None, + factory=True, + mcp=None, + ), + get=Reader( + request=None, + response=TranscriptGetResponse, + description="Get the list of `messages` " + "that make up this Transcript. Each " + "message has a `role` (the name of " + "whoever added the message, e.g., `user` " + "or `assistant`) and a `content` string.", + mcp=Tool(), + ), + update=Writer( + request=TranscriptUpdateRequest, + response=None, + description="Replace this Transcript's " + "messages with a new list. Each message is " + "a {role, content} entry. Any publicly " + "available links referenced in the " + "conversation — for example URLs to " + "images — should be included verbatim in " + "the message `content`.", + mcp=Tool(), + ), + ), + ), +) diff --git a/reboot/examples/agent-wiki/backend/.pytest.ini b/reboot/examples/agent-wiki/backend/.pytest.ini new file mode 100644 index 00000000..7384e721 --- /dev/null +++ b/reboot/examples/agent-wiki/backend/.pytest.ini @@ -0,0 +1,14 @@ +[pytest] +# `agent_wiki.v1` is a namespace package split across the +# top-level `api/` directory (Pydantic API definitions) and +# `backend/api/` (generated Reboot bindings), so both must be +# on `sys.path`. `src/` carries `main.py` and the `servicers/` +# package. Paths are relative to this file's directory. +pythonpath= + src/ + ../api/ + api/ +# Tests end in `_test.py` to match the convention used by +# sibling Reboot examples (`*_test.py` is one of pytest's +# default discovery patterns alongside `test_*.py`). +python_files=*_test.py diff --git a/reboot/examples/agent-wiki/backend/src/main.py b/reboot/examples/agent-wiki/backend/src/main.py new file mode 100644 index 00000000..ed907c2e --- /dev/null +++ b/reboot/examples/agent-wiki/backend/src/main.py @@ -0,0 +1,34 @@ +import asyncio +import logging +from reboot.aio.applications import Application +from reboot.aio.auth.oauth_providers import Anonymous +from servicers.wiki import ( + PageServicer, + TranscriptServicer, + UserServicer, + WikiServicer, +) + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", +) + + +async def main() -> None: + application = Application( + servicers=[ + UserServicer, + WikiServicer, + PageServicer, + TranscriptServicer, + ], + # `User` is an auto-constructed state type, so Reboot + # needs an OAuth provider to identify the caller. + oauth=Anonymous(), + ) + await application.run() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/reboot/examples/agent-wiki/backend/src/servicers/wiki.py b/reboot/examples/agent-wiki/backend/src/servicers/wiki.py new file mode 100644 index 00000000..2256525f --- /dev/null +++ b/reboot/examples/agent-wiki/backend/src/servicers/wiki.py @@ -0,0 +1,463 @@ +import asyncio +import json +import logging +import pydantic_ai +from agent_wiki.v1.wiki import ( + UserCreateWikiRequest, + UserCreateWikiResponse, + UserListWikisResponse, + WikiAddTranscriptResponse, + WikiSummary, +) +from agent_wiki.v1.wiki_rbt import Page, Transcript, User, Wiki +from dataclasses import dataclass +from pydantic_ai import RunContext +from reboot.agents.pydantic_ai import Agent +from reboot.aio.contexts import ( + ReaderContext, + TransactionContext, + WorkflowContext, + WriterContext, +) +from reboot.aio.workflows import at_most_once, until + +logger = logging.getLogger(__name__) + + +def _truncate(value: object, limit: int = 500) -> str: + """Render `value` as a string, shortened for log lines so a + big tool payload doesn't flood the output.""" + text = value if isinstance(value, str) else repr(value) + if len(text) <= limit: + return text + return text[:limit] + f"... [+{len(text) - limit} chars]" + + +def _log_librarian_node(prefix: str, node: object) -> None: + """Log one step of a `librarian.iter(...)` run. Called + for every node yielded by the iterator so the backend + log shows the model's thoughts, its tool calls, and + each tool's return value as the ingest happens.""" + if pydantic_ai.Agent.is_user_prompt_node(node): + logger.info("%s prompt submitted", prefix) + elif pydantic_ai.Agent.is_model_request_node(node): + # `node.request.parts` holds the tool results (and + # any user prompts) being fed back to the model. + for part in node.request.parts: + if getattr(part, "part_kind", None) == "tool-return": + logger.info( + "%s tool return %s -> %s", + prefix, + part.tool_name, + _truncate(part.content), + ) + elif pydantic_ai.Agent.is_call_tools_node(node): + # `node.model_response.parts` is the model's latest + # reply: free-form text (its "thinking") and the + # tool calls it wants us to execute. + for part in node.model_response.parts: + kind = getattr(part, "part_kind", None) + if kind == "text": + logger.info( + "%s thinking: %s", + prefix, + _truncate(part.content), + ) + elif kind == "tool-call": + logger.info( + "%s tool call %s(%s)", + prefix, + part.tool_name, + _truncate(part.args), + ) + elif pydantic_ai.Agent.is_end_node(node): + logger.info("%s done", prefix) + else: + logger.debug("%s unknown node: %r", prefix, node) + + +@dataclass +class LibrarianDeps: + wiki_id: str + + +librarian = Agent( + # NOTE: Pydantic AI reads the Anthropic API key from the + # `ANTHROPIC_API_KEY` environment variable. + "anthropic:claude-sonnet-4-6", + name="librarian", + deps_type=LibrarianDeps, + system_prompt=( + "You are the librarian of a knowledge base wiki. " + "Your job is to progressively AGGREGATE knowledge " + "from conversation transcripts into a small, " + "well-organized set of pages — NOT to accumulate " + "one page per transcript.\n\n" + "Each transcript you receive was a conversation a " + "user had and wants captured. Fold its key facts " + "into the wiki while actively looking for " + "opportunities to consolidate, generalize, and " + "rename existing pages so that related material " + "ends up together under a broader heading.\n\n" + "Example of the behavior we want: suppose the " + "wiki already has a page titled \"History of the " + "Steam Engine\". A new transcript arrives about " + "the history of the light bulb. Rather than " + "creating a separate page, you should RENAME the " + "existing page to something like \"History of " + "Inventions\" and rewrite its content to cover " + "both. If a later transcript covers the history " + "of Greece, broaden again — maybe \"History of " + "Inventions and Countries\", or just \"History of " + "Things\" once the scope is wide enough. Keep " + "generalizing as the material accumulates.\n\n" + "Your workflow on each transcript:\n" + "1. Call `get_wiki` to read the wiki's current " + "markdown body. That markdown is the table of " + "contents; pages are referenced as " + "`Page:` URIs.\n" + "2. Decide which existing page (if any) is the " + "best home for the new material. Prefer " + "broadening an existing page over creating a new " + "one. Call `get_page` to read candidates.\n" + "3. If a page can absorb this material (possibly " + "after renaming and partial rewrite), call " + "`update_page` with a new `title` and rewritten " + "`content`. It is completely fine to rewrite a " + "page from scratch when that produces a cleaner " + "summary.\n" + "4. Only call `create_page` when the new material " + "genuinely does not fit any existing page even " + "after generalizing.\n" + "5. Call `update_wiki` to rewrite the wiki's " + "markdown body so the table of contents reflects " + "the current page titles and IDs. Rewriting the " + "wiki wholesale is fine.\n\n" + "Important habits:\n" + "- Pages should be DISTILLED summaries — facts " + "organized into concise prose — not verbatim " + "dumps of the transcript. Aim for fewer, " + "higher-signal pages.\n" + "- You do not need to inline everything from a " + "transcript. Linking to the source transcript as " + "`Transcript:` from within a page's " + "markdown is a perfectly good way to preserve " + "detail without bloating the page.\n" + "- Err on the side of fewer, broader pages. If " + "two pages cover closely related ground, merge " + "them.\n" + "- Use `Page:` and " + "`Transcript:` URIs liberally so " + "readers can follow links by calling the " + "referenced type's `get` method with the " + "embedded state ID." + ), +) + + +@librarian.tool +async def get_wiki( + context: WorkflowContext, + run_context: RunContext[LibrarianDeps], +) -> dict: + """Read the wiki's current name, description, and + markdown content.""" + state = await Wiki.ref( + run_context.deps.wiki_id, + ).get(context) + return { + "name": state.name, + "description": state.description, + "content": state.content, + } + + +@librarian.tool +async def update_wiki( + context: WorkflowContext, + run_context: RunContext[LibrarianDeps], + content: str, +) -> None: + """Replace the wiki's markdown body. Put the table of + contents, cross-references, and any structure directly + in this markdown. Link to pages with `Page:` + URIs.""" + await Wiki.ref( + run_context.deps.wiki_id, + ).update( + context, + content=content, + ) + + +@librarian.tool +async def get_page( + context: WorkflowContext, + run_context: RunContext[LibrarianDeps], + page_id: str, +) -> dict: + """Read a page's title and markdown content.""" + state = await Page.ref(page_id).get(context) + return { + "title": state.title, + "content": state.content, + } + + +@librarian.tool +async def update_page( + context: WorkflowContext, + run_context: RunContext[LibrarianDeps], + page_id: str, + title: str, + content: str, +) -> None: + """Replace a page's title and markdown body. Use this to + rename a page as its scope broadens (e.g., a page about + the steam engine becoming "History of Inventions"), or + to rewrite its content to incorporate new material.""" + await Page.ref(page_id).update( + context, + title=title, + content=content, + ) + + +@librarian.tool +async def create_page( + context: WorkflowContext, + run_context: RunContext[LibrarianDeps], + title: str, + content: str, +) -> str: + """Create a new page with the given title and markdown + content. Returns the new page's state ID, which you + should link to from the wiki's markdown as + `Page:`.""" + page, _ = await Page.create( + context, + title=title, + content=content, + ) + return page.state_id + + +class UserServicer(User.Servicer): + """Servicer for the per-user state machine; entry point to + the Wiki knowledge base.""" + + async def create_wiki( + self, + context: TransactionContext, + request: UserCreateWikiRequest, + ) -> UserCreateWikiResponse: + """Create a new Wiki, record it on the user under + the given name, and kick off its ingest workflow.""" + wiki, _ = await Wiki.create( + context, + name=request.name, + description=request.description, + ) + self.state.wikis[request.name] = wiki.state_id + return UserCreateWikiResponse(wiki_id=wiki.state_id) + + async def list_wikis( + self, + context: ReaderContext, + ) -> UserListWikisResponse: + """List every wiki owned by this user.""" + wikis: list[WikiSummary] = [] + for name, wiki_id in self.state.wikis.items(): + state = await Wiki.ref(wiki_id).get(context) + wikis.append( + WikiSummary( + wiki_id=wiki_id, + name=name, + description=state.description, + ) + ) + return UserListWikisResponse(wikis=wikis) + + +class WikiServicer(Wiki.Servicer): + """Servicer for an individual Wiki: a markdown body plus + title and description metadata, and the librarian + `ingest` workflow that folds transcripts into it.""" + + async def create( + self, + context: WriterContext, + request: Wiki.CreateRequest, + ) -> None: + self.state.name = request.name + self.state.description = request.description + await self.ref().schedule().ingest(context) + + async def get( + self, + context: ReaderContext, + ) -> Wiki.GetResponse: + return Wiki.GetResponse( + name=self.state.name, + description=self.state.description, + content=self.state.content, + ) + + async def update( + self, + context: WriterContext, + request: Wiki.UpdateRequest, + ) -> None: + self.state.content = request.content + + async def add_transcript( + self, + context: TransactionContext, + request: Wiki.AddTranscriptRequest, + ) -> WikiAddTranscriptResponse: + """Create a new Transcript belonging to this Wiki and + record it as pending ingestion on the Wiki.""" + transcript, _ = await Transcript.create( + context, + messages=list(request.messages), + ) + self.state.transcripts[transcript.state_id] = False + return WikiAddTranscriptResponse( + transcript_id=transcript.state_id, + ) + + @classmethod + async def ingest( + cls, + context: WorkflowContext, + ) -> None: + """Librarian workflow: watch this Wiki's transcripts + map for pending entries, run the LLM librarian on + each to fold it into the wiki's markdown and into + Pages, then mark the transcript ingested.""" + wiki = Wiki.ref() + wiki_id = wiki.state_id + + async for _ in context.loop("Ingest loop"): + + async def find_pending_transcript() -> str | bool: + state = await wiki.read(context) + for transcript_id, ingested in state.transcripts.items(): + if not ingested: + return transcript_id + return False + + transcript_id = await until( + "Pending transcript", + context, + find_pending_transcript, + type=str, + ) + + transcript = await Transcript.ref(transcript_id).get(context) + conversation = json.dumps( + [message.model_dump() for message in transcript.messages], + indent=2, + ) + + prompt = ( + "Ingest this conversation transcript into " + "the wiki. The transcript is provided below " + "as a JSON array of {role, content} " + f"messages:\n\n{conversation}" + ) + log_prefix = ( + f"librarian[wiki={wiki_id} " + f"transcript={transcript_id}]" + ) + + async def run_librarian() -> str: + async with librarian.iter( + context, + prompt, + deps=LibrarianDeps(wiki_id=wiki_id), + ) as run: + async for node in run: + _log_librarian_node(log_prefix, node) + assert run.result is not None + return str(run.result.output) + + try: + await at_most_once( + f"Ingest {transcript_id}", + context, + run_librarian, + type=str, + ) + except asyncio.CancelledError: + # Explicitly propagate cancellation so the workflow can + # be stopped i.e. during the test teardown. + raise + except: + import traceback + traceback.print_exc() + # TODO: better error handling, i.e., some errors might + # be retryable and others perhaps we just skip this + # transcript as we are currently doing? + + async def mark_ingested(state): + state.transcripts[transcript_id] = True + + await wiki.write(context, mark_ingested) + + +class PageServicer(Page.Servicer): + """Servicer for an individual Page: a markdown body with + a title.""" + + async def create( + self, + context: WriterContext, + request: Page.CreateRequest, + ) -> None: + self.state.title = request.title + self.state.content = request.content + + async def get( + self, + context: ReaderContext, + ) -> Page.GetResponse: + return Page.GetResponse( + title=self.state.title, + content=self.state.content, + ) + + async def update( + self, + context: WriterContext, + request: Page.UpdateRequest, + ) -> None: + self.state.title = request.title + self.state.content = request.content + + +class TranscriptServicer(Transcript.Servicer): + """Servicer for an individual Transcript (raw conversation + transcript).""" + + async def create( + self, + context: WriterContext, + request: Transcript.CreateRequest, + ) -> None: + self.state.messages = list(request.messages) + + async def get( + self, + context: ReaderContext, + ) -> Transcript.GetResponse: + return Transcript.GetResponse( + messages=list(self.state.messages), + ) + + async def update( + self, + context: WriterContext, + request: Transcript.UpdateRequest, + ) -> None: + self.state.messages = list(request.messages) diff --git a/reboot/examples/agent-wiki/backend/tests/conftest.py b/reboot/examples/agent-wiki/backend/tests/conftest.py new file mode 100644 index 00000000..accd417f --- /dev/null +++ b/reboot/examples/agent-wiki/backend/tests/conftest.py @@ -0,0 +1,14 @@ +"""Pytest bootstrap for the agent-wiki backend tests. + +The `librarian` Pydantic AI agent in `servicers/wiki.py` is +constructed at module import time and eagerly instantiates +the Anthropic provider, which fails if `ANTHROPIC_API_KEY` +is unset. Our tests replace the model with a scripted +`FunctionModel` before any Anthropic request is made, but +we still need a non-empty key present at import. Provide a +placeholder here so the module loads even when the developer +has no real key in their environment — e.g., in CI. +""" +import os + +os.environ.setdefault("ANTHROPIC_API_KEY", "test-placeholder") diff --git a/reboot/examples/agent-wiki/backend/tests/wiki_test.py b/reboot/examples/agent-wiki/backend/tests/wiki_test.py new file mode 100644 index 00000000..f43490d2 --- /dev/null +++ b/reboot/examples/agent-wiki/backend/tests/wiki_test.py @@ -0,0 +1,409 @@ +"""Tests for the agent-wiki backend. + +Covers: +* CRUD on each servicer (`User`, `Wiki`, `Page`, + `Transcript`) via direct Reboot calls. +* The end-to-end `Wiki.ingest` librarian workflow, with the + real Anthropic model swapped for a scripted Pydantic AI + `FunctionModel` so no external API call is made. +""" +import asyncio +import unittest +from agent_wiki.v1.wiki import TranscriptMessage +from agent_wiki.v1.wiki_rbt import Page, Transcript, User, Wiki +from pydantic_ai.messages import ( + ModelMessage, + ModelResponse, + TextPart, + ToolCallPart, +) +from pydantic_ai.models.function import AgentInfo, FunctionModel +from reboot.aio.applications import Application +from reboot.aio.auth.authorizers import allow +from reboot.aio.tests import Reboot +from servicers import wiki as wiki_module +from servicers.wiki import ( + PageServicer, + TranscriptServicer, + UserServicer, + WikiServicer, +) + +# Production servicers intentionally don't define an +# `authorizer()`: in development Reboot defaults to allow-all, +# but in production an absent authorizer denies by default, +# which we rely on so that no permissive code accidentally +# ships. The tests run against the production-mode harness, so +# we extend each servicer here and grant `allow()` for the +# duration of the suite. + + +class PermissiveUserServicer(UserServicer): + + def authorizer(self): + return allow() + + +class PermissiveWikiServicer(WikiServicer): + + def authorizer(self): + return allow() + + +class PermissivePageServicer(PageServicer): + + def authorizer(self): + return allow() + + +class PermissiveTranscriptServicer(TranscriptServicer): + + def authorizer(self): + return allow() + + +APPLICATION_SERVICERS = [ + PermissiveUserServicer, + PermissiveWikiServicer, + PermissivePageServicer, + PermissiveTranscriptServicer, +] + + +def _null_librarian_model() -> FunctionModel: + """Return a `FunctionModel` that refuses to be called. + Used by tests that should never trigger the librarian; + if they accidentally do, we get a clear failure instead + of a real Anthropic request.""" + + def _refuse( + messages: list[ModelMessage], + info: AgentInfo, + ) -> ModelResponse: + raise AssertionError( + "Librarian invoked in a test that should not " + "trigger ingestion." + ) + + return FunctionModel(_refuse) + + +class ServicerTest(unittest.IsolatedAsyncioTestCase): + """Unit tests for each servicer's CRUD methods. These + tests never add a transcript, so the librarian workflow + never actually runs — but we still replace the agent's + model as a belt-and-braces guard against accidental + Anthropic calls from this suite.""" + + async def asyncSetUp(self) -> None: + self._original_model = wiki_module.librarian.wrapped.model + wiki_module.librarian.wrapped.model = _null_librarian_model() + + self.rbt = Reboot() + await self.rbt.start() + await self.rbt.up( + Application(servicers=APPLICATION_SERVICERS), + ) + self.user_id = "alice" + self.context = self.rbt.create_external_context( + name=f"test-{self.id()}", + bearer_token=self.rbt.make_valid_oauth_access_token( + user_id=self.user_id, + ), + ) + # `User` is an auto-constructed state type: in + # production the MCP session's "new session" hook + # calls `_auto_construct` for the authenticated user. + # Tests don't go through that hook, so we do it here. + await PermissiveUserServicer._auto_construct( + self.context, + state_id=self.user_id, + ) + + async def asyncTearDown(self) -> None: + await self.rbt.stop() + wiki_module.librarian.wrapped.model = self._original_model + + async def test_user_create_and_list_wikis(self) -> None: + """A user can create a wiki and then see it in their + list, keyed by the user-supplied name.""" + user = User.ref("alice") + create_response = await user.create_wiki( + self.context, + name="my notes", + description="my personal notes", + ) + self.assertTrue(create_response.wiki_id) + + list_response = await user.list_wikis(self.context) + self.assertEqual(len(list_response.wikis), 1) + (summary,) = list_response.wikis + self.assertEqual(summary.wiki_id, create_response.wiki_id) + self.assertEqual(summary.name, "my notes") + self.assertEqual(summary.description, "my personal notes") + + async def test_wiki_get_and_update(self) -> None: + """A freshly created wiki exposes its name and + description, starts with empty markdown, and + `update` replaces the markdown body.""" + user = User.ref("alice") + create_response = await user.create_wiki( + self.context, + name="my notes", + description="my personal notes", + ) + wiki = Wiki.ref(create_response.wiki_id) + + got = await wiki.get(self.context) + self.assertEqual(got.name, "my notes") + self.assertEqual(got.description, "my personal notes") + self.assertEqual(got.content, "") + + await wiki.update(self.context, content="# Hello\n") + got = await wiki.get(self.context) + self.assertEqual(got.content, "# Hello\n") + + async def test_page_crud(self) -> None: + """`Page.create` / `get` / `update` round-trip the + title and markdown body.""" + page, _ = await Page.create( + self.context, + title="My Page", + content="Initial body.", + ) + got = await page.get(self.context) + self.assertEqual(got.title, "My Page") + self.assertEqual(got.content, "Initial body.") + + await page.update( + self.context, + title="Renamed Page", + content="New body.", + ) + got = await page.get(self.context) + self.assertEqual(got.title, "Renamed Page") + self.assertEqual(got.content, "New body.") + + async def test_transcript_crud(self) -> None: + """`Transcript.create` / `get` / `update` round-trip + a list of `{role, content}` messages.""" + messages = [ + TranscriptMessage(role="user", content="Hello"), + TranscriptMessage(role="assistant", content="Hi!"), + ] + transcript, _ = await Transcript.create( + self.context, + messages=messages, + ) + got = await transcript.get(self.context) + self.assertEqual(len(got.messages), 2) + self.assertEqual(got.messages[0].role, "user") + self.assertEqual(got.messages[0].content, "Hello") + self.assertEqual(got.messages[1].role, "assistant") + self.assertEqual(got.messages[1].content, "Hi!") + + await transcript.update( + self.context, + messages=[ + TranscriptMessage(role="user", content="Goodbye"), + ], + ) + got = await transcript.get(self.context) + self.assertEqual(len(got.messages), 1) + self.assertEqual(got.messages[0].content, "Goodbye") + + async def test_add_transcript_creates_transcript( + self, + ) -> None: + """`Wiki.add_transcript` creates a new `Transcript` + whose state reflects the given messages and returns + its ID.""" + user = User.ref("alice") + create_response = await user.create_wiki( + self.context, + name="notes", + description="", + ) + wiki = Wiki.ref(create_response.wiki_id) + + add_response = await wiki.add_transcript( + self.context, + messages=[ + TranscriptMessage(role="user", content="Hi."), + TranscriptMessage(role="assistant", content="Hello!"), + ], + ) + self.assertTrue(add_response.transcript_id) + + transcript = await Transcript.ref(add_response.transcript_id + ).get(self.context) + self.assertEqual(len(transcript.messages), 2) + self.assertEqual(transcript.messages[0].content, "Hi.") + self.assertEqual(transcript.messages[1].content, "Hello!") + + +class ScriptedLibrarian: + """A stateful scripted Pydantic AI model that drives the + librarian through a fixed sequence of tool calls: + + get_wiki -> create_page -> update_wiki -> end + + Each call sees the agent's conversation history and + decides what to do next based on which tools have + already returned, so the script is robust to any Reboot- + level retries or extra round-trips. The `page_id` + produced by `create_page` is extracted from its tool + return and woven into the `update_wiki` call.""" + + PAGE_TITLE = "Test Page" + PAGE_CONTENT = "Distilled transcript content." + + def __init__(self) -> None: + self.page_id: str | None = None + + def step( + self, + messages: list[ModelMessage], + info: AgentInfo, + ) -> ModelResponse: + # Collect the names of tools whose returns we've + # already observed. The librarian is deterministic + # so this is enough to drive the next step. + returned_tools: set[str] = set() + for message in messages: + for part in getattr(message, "parts", []): + if getattr(part, "part_kind", None) != "tool-return": + continue + returned_tools.add(part.tool_name) + # The `create_page` tool returns the new + # page's state ID as a bare string; remember + # it for the `update_wiki` call. + if part.tool_name == "create_page": + self.page_id = str(part.content) + + if "get_wiki" not in returned_tools: + return ModelResponse( + parts=[ + ToolCallPart(tool_name="get_wiki", args={}), + ] + ) + if "create_page" not in returned_tools: + return ModelResponse( + parts=[ + ToolCallPart( + tool_name="create_page", + args={ + "title": self.PAGE_TITLE, + "content": self.PAGE_CONTENT, + }, + ), + ] + ) + if "update_wiki" not in returned_tools: + assert self.page_id is not None, ( + "create_page must have returned before " + "update_wiki" + ) + return ModelResponse( + parts=[ + ToolCallPart( + tool_name="update_wiki", + args={ + "content": + ( + "# Table of contents\n\n" + f"- [Test Page](Page:{self.page_id})\n" + ), + }, + ), + ] + ) + return ModelResponse(parts=[TextPart(content="Done.")]) + + +class IngestWorkflowTest(unittest.IsolatedAsyncioTestCase): + """End-to-end test of the `Wiki.ingest` librarian + workflow with the LLM replaced by a `FunctionModel`.""" + + async def asyncSetUp(self) -> None: + self.script = ScriptedLibrarian() + self._original_model = wiki_module.librarian.wrapped.model + wiki_module.librarian.wrapped.model = FunctionModel(self.script.step) + + self.rbt = Reboot() + await self.rbt.start() + await self.rbt.up( + Application(servicers=APPLICATION_SERVICERS), + ) + self.user_id = "alice" + self.context = self.rbt.create_external_context( + name=f"test-{self.id()}", + bearer_token=self.rbt.make_valid_oauth_access_token( + user_id=self.user_id, + ), + ) + # `User` is an auto-constructed state type: in + # production the MCP session's "new session" hook + # calls `_auto_construct` for the authenticated user. + # Tests don't go through that hook, so we do it here. + await PermissiveUserServicer._auto_construct( + self.context, + state_id=self.user_id, + ) + + async def asyncTearDown(self) -> None: + await self.rbt.stop() + wiki_module.librarian.wrapped.model = self._original_model + + async def test_ingest_creates_page_and_updates_wiki( + self, + ) -> None: + """Adding a transcript wakes the librarian, which + runs the scripted `get_wiki -> create_page -> + update_wiki` sequence and marks the transcript + ingested. We verify the wiki's markdown was rewritten + and that the referenced page actually exists with + the scripted title and body.""" + user = User.ref("alice") + create_response = await user.create_wiki( + self.context, + name="notes", + description="knowledge base", + ) + wiki = Wiki.ref(create_response.wiki_id) + + await wiki.add_transcript( + self.context, + messages=[ + TranscriptMessage(role="user", content="Tell me about X."), + TranscriptMessage( + role="assistant", + content="X is a thing that does Y.", + ), + ], + ) + + # Poll the wiki's markdown body until the scripted + # `update_wiki` call lands. `Wiki.get` is the only + # externally observable signal — `transcripts` lives + # on the internal state, not on the `get` response. + for _ in range(100): # 10 s at 100 ms steps. + state = await wiki.get(self.context) + if state.content.startswith("# Table of contents"): + break + await asyncio.sleep(0.1) + else: + self.fail( + "Timed out waiting for librarian to rewrite " + "Wiki.content" + ) + + # The scripted librarian should have created exactly + # one page and referenced it from the wiki's + # markdown. + self.assertIsNotNone(self.script.page_id) + self.assertIn(f"Page:{self.script.page_id}", state.content) + + page = await Page.ref(self.script.page_id).get(self.context) + self.assertEqual(page.title, ScriptedLibrarian.PAGE_TITLE) + self.assertEqual(page.content, ScriptedLibrarian.PAGE_CONTENT) diff --git a/reboot/examples/agent-wiki/mcp_servers.json b/reboot/examples/agent-wiki/mcp_servers.json new file mode 100644 index 00000000..667a4b7c --- /dev/null +++ b/reboot/examples/agent-wiki/mcp_servers.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "agent-wiki": { + "url": "http://localhost:9991/mcp", + "useOAuth": true + } + } +} diff --git a/reboot/examples/agent-wiki/pyproject.toml b/reboot/examples/agent-wiki/pyproject.toml new file mode 100644 index 00000000..6db12ed3 --- /dev/null +++ b/reboot/examples/agent-wiki/pyproject.toml @@ -0,0 +1,21 @@ +[project] +name = "agent-wiki" +version = "0.1.0" +requires-python = ">= 3.10" +dependencies = [ + "httpx>=0.27,<1.0", + "uuid7>=0.1.0", + "anyio>=4.0.0", + "pydantic-ai-slim[anthropic]>=1.0.0", + "reboot==1.0.3", +] + +[tool.rye] +dev-dependencies = [ + "pytest>=7.4", + "reboot==1.0.3", +] + +# This project only uses `rye` to provide `python` and its dependencies. +virtual = true +managed = true diff --git a/reboot/examples/agent-wiki/requirements-dev.lock b/reboot/examples/agent-wiki/requirements-dev.lock new file mode 100644 index 00000000..d806d328 --- /dev/null +++ b/reboot/examples/agent-wiki/requirements-dev.lock @@ -0,0 +1,301 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false + +aiofiles==23.2.1 + # via reboot +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.2 + # via kubernetes-asyncio + # via reboot +aiosignal==1.4.0 + # via aiohttp +annotated-types==0.7.0 + # via pydantic +anthropic==0.97.0 + # via pydantic-ai-slim +anyio==4.13.0 + # via anthropic + # via httpx + # via mcp + # via reboot + # via sse-starlette + # via starlette +async-timeout==5.0.1 + # via aiohttp +attrs==26.1.0 + # via aiohttp + # via jsonschema + # via referencing +bitarray==3.8.0 + # via reboot +certifi==2026.4.22 + # via httpcore + # via httpx + # via kubernetes-asyncio +cffi==1.17.1 + # via cryptography + # via reboot +click==8.3.3 + # via uvicorn +colorama==0.4.6 + # via reboot +cryptography==44.0.0 + # via pyjwt + # via reboot +deprecated==1.3.1 + # via opentelemetry-api + # via opentelemetry-exporter-otlp-proto-grpc + # via opentelemetry-semantic-conventions +distro==1.9.0 + # via anthropic +docstring-parser==0.18.0 + # via anthropic +exceptiongroup==1.3.1 + # via anyio + # via pydantic-ai-slim + # via pytest +fastapi==0.115.12 + # via reboot +frozenlist==1.8.0 + # via aiohttp + # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim +googleapis-common-protos==1.65.0 + # via grpcio-status + # via opentelemetry-exporter-otlp-proto-grpc + # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim +grpc-interceptor==0.15.4 + # via reboot +grpcio==1.64.3 + # via grpc-interceptor + # via grpcio-health-checking + # via grpcio-reflection + # via grpcio-status + # via grpcio-tools + # via opentelemetry-exporter-otlp-proto-grpc + # via reboot +grpcio-health-checking==1.64.3 + # via reboot +grpcio-reflection==1.64.3 + # via reboot +grpcio-status==1.64.3 + # via reboot +grpcio-tools==1.64.3 + # via reboot +h11==0.16.0 + # via httpcore + # via uvicorn +h2==4.3.0 + # via reboot +hpack==4.1.0 + # via h2 +httpcore==1.0.9 + # via httpx +httpx==0.28.1 + # via anthropic + # via genai-prices + # via mcp + # via pydantic-ai-slim + # via pydantic-graph +httpx-sse==0.4.3 + # via mcp +hyperframe==6.1.0 + # via h2 +idna==3.13 + # via anyio + # via httpx + # via yarl +importlib-metadata==8.5.0 + # via opentelemetry-api +iniconfig==2.3.0 + # via pytest +jinja2==3.1.2 + # via jinja2-strcase + # via reboot +jinja2-strcase==0.0.2 + # via reboot +jiter==0.14.0 + # via anthropic +jsonschema==4.26.0 + # via mcp +jsonschema-specifications==2025.9.1 + # via jsonschema +kubernetes-asyncio==31.1.0 + # via reboot +logfire-api==4.32.1 + # via pydantic-graph +markupsafe==3.0.3 + # via jinja2 +mcp==1.27.0 + # via reboot +multidict==6.7.1 + # via aiohttp + # via yarl +mypy-protobuf==3.6.0 + # via reboot +opentelemetry-api==1.28.1 + # via opentelemetry-exporter-otlp-proto-grpc + # via opentelemetry-instrumentation + # via opentelemetry-instrumentation-grpc + # via opentelemetry-sdk + # via opentelemetry-semantic-conventions + # via pydantic-ai-slim + # via reboot +opentelemetry-exporter-otlp-proto-common==1.28.1 + # via opentelemetry-exporter-otlp-proto-grpc +opentelemetry-exporter-otlp-proto-grpc==1.28.1 + # via reboot +opentelemetry-instrumentation==0.49b1 + # via opentelemetry-instrumentation-grpc +opentelemetry-instrumentation-grpc==0.49b1 + # via reboot +opentelemetry-proto==1.28.1 + # via opentelemetry-exporter-otlp-proto-common + # via opentelemetry-exporter-otlp-proto-grpc +opentelemetry-sdk==1.28.1 + # via opentelemetry-exporter-otlp-proto-grpc + # via reboot +opentelemetry-semantic-conventions==0.49b1 + # via opentelemetry-instrumentation + # via opentelemetry-instrumentation-grpc + # via opentelemetry-sdk +packaging==26.2 + # via opentelemetry-instrumentation + # via pytest + # via reboot +pathspec==0.12.1 + # via reboot +pluggy==1.6.0 + # via pytest +propcache==0.4.1 + # via aiohttp + # via yarl +protobuf==5.28.3 + # via googleapis-common-protos + # via grpcio-health-checking + # via grpcio-reflection + # via grpcio-status + # via grpcio-tools + # via mypy-protobuf + # via opentelemetry-proto + # via reboot +psutil==6.0.0 + # via reboot +pycparser==3.0 + # via cffi +pydantic==2.13.3 + # via anthropic + # via fastapi + # via genai-prices + # via mcp + # via pydantic-ai-slim + # via pydantic-graph + # via pydantic-settings + # via reboot +pydantic-ai-slim==1.87.0 + # via reboot +pydantic-core==2.46.3 + # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim +pydantic-settings==2.14.0 + # via mcp +pygments==2.20.0 + # via pytest +pyjwt==2.10.1 + # via mcp + # via reboot +pyprctl==0.1.3 + # via reboot +pytest==9.0.3 +python-dateutil==2.9.0.post0 + # via kubernetes-asyncio +python-dotenv==1.2.2 + # via pydantic-settings +python-multipart==0.0.27 + # via mcp +python-ulid==3.1.0 + # via reboot +pyyaml==6.0.2 + # via kubernetes-asyncio + # via reboot +reboot==1.0.3 +referencing==0.37.0 + # via jsonschema + # via jsonschema-specifications +rpds-py==0.30.0 + # via jsonschema + # via referencing +setuptools==82.0.1 + # via grpcio-tools +six==1.17.0 + # via kubernetes-asyncio + # via python-dateutil +sniffio==1.3.1 + # via anthropic +sse-starlette==3.0.3 + # via mcp +starlette==0.46.2 + # via fastapi + # via mcp + # via reboot +tabulate==0.9.0 + # via reboot +tomli==2.4.1 + # via pytest +types-protobuf==7.34.1.20260408 + # via mypy-protobuf +typing-extensions==4.15.0 + # via aiosignal + # via anthropic + # via anyio + # via exceptiongroup + # via fastapi + # via mcp + # via multidict + # via opentelemetry-sdk + # via pydantic + # via pydantic-core + # via reboot + # via referencing + # via typing-inspection + # via uvicorn +typing-inspection==0.4.2 + # via mcp + # via pydantic + # via pydantic-ai-slim + # via pydantic-graph + # via pydantic-settings +tzlocal==5.3 + # via reboot +urllib3==1.26.15 + # via kubernetes-asyncio + # via reboot +uuid7==0.1.0 +uuid7-standard==1.1.0 + # via reboot +uvicorn==0.34.0 + # via mcp + # via reboot +watchdog==6.0.0 + # via reboot +websockets==15.0.1 + # via reboot +wrapt==1.17.3 + # via deprecated + # via opentelemetry-instrumentation + # via opentelemetry-instrumentation-grpc +yarl==1.23.0 + # via aiohttp +zipp==3.23.1 + # via importlib-metadata diff --git a/reboot/examples/agent-wiki/requirements.lock b/reboot/examples/agent-wiki/requirements.lock new file mode 100644 index 00000000..eb616862 --- /dev/null +++ b/reboot/examples/agent-wiki/requirements.lock @@ -0,0 +1,290 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false + +aiofiles==23.2.1 + # via reboot +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.2 + # via kubernetes-asyncio + # via reboot +aiosignal==1.4.0 + # via aiohttp +annotated-types==0.7.0 + # via pydantic +anthropic==0.97.0 + # via pydantic-ai-slim +anyio==4.13.0 + # via anthropic + # via httpx + # via mcp + # via reboot + # via sse-starlette + # via starlette +async-timeout==5.0.1 + # via aiohttp +attrs==26.1.0 + # via aiohttp + # via jsonschema + # via referencing +bitarray==3.8.0 + # via reboot +certifi==2026.4.22 + # via httpcore + # via httpx + # via kubernetes-asyncio +cffi==1.17.1 + # via cryptography + # via reboot +click==8.3.3 + # via uvicorn +colorama==0.4.6 + # via reboot +cryptography==44.0.0 + # via pyjwt + # via reboot +deprecated==1.3.1 + # via opentelemetry-api + # via opentelemetry-exporter-otlp-proto-grpc + # via opentelemetry-semantic-conventions +distro==1.9.0 + # via anthropic +docstring-parser==0.18.0 + # via anthropic +exceptiongroup==1.3.1 + # via anyio + # via pydantic-ai-slim +fastapi==0.115.12 + # via reboot +frozenlist==1.8.0 + # via aiohttp + # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim +googleapis-common-protos==1.65.0 + # via grpcio-status + # via opentelemetry-exporter-otlp-proto-grpc + # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim +grpc-interceptor==0.15.4 + # via reboot +grpcio==1.64.3 + # via grpc-interceptor + # via grpcio-health-checking + # via grpcio-reflection + # via grpcio-status + # via grpcio-tools + # via opentelemetry-exporter-otlp-proto-grpc + # via reboot +grpcio-health-checking==1.64.3 + # via reboot +grpcio-reflection==1.64.3 + # via reboot +grpcio-status==1.64.3 + # via reboot +grpcio-tools==1.64.3 + # via reboot +h11==0.16.0 + # via httpcore + # via uvicorn +h2==4.3.0 + # via reboot +hpack==4.1.0 + # via h2 +httpcore==1.0.9 + # via httpx +httpx==0.28.1 + # via anthropic + # via genai-prices + # via mcp + # via pydantic-ai-slim + # via pydantic-graph +httpx-sse==0.4.3 + # via mcp +hyperframe==6.1.0 + # via h2 +idna==3.13 + # via anyio + # via httpx + # via yarl +importlib-metadata==8.5.0 + # via opentelemetry-api +jinja2==3.1.2 + # via jinja2-strcase + # via reboot +jinja2-strcase==0.0.2 + # via reboot +jiter==0.14.0 + # via anthropic +jsonschema==4.26.0 + # via mcp +jsonschema-specifications==2025.9.1 + # via jsonschema +kubernetes-asyncio==31.1.0 + # via reboot +logfire-api==4.32.1 + # via pydantic-graph +markupsafe==3.0.3 + # via jinja2 +mcp==1.27.0 + # via reboot +multidict==6.7.1 + # via aiohttp + # via yarl +mypy-protobuf==3.6.0 + # via reboot +opentelemetry-api==1.28.1 + # via opentelemetry-exporter-otlp-proto-grpc + # via opentelemetry-instrumentation + # via opentelemetry-instrumentation-grpc + # via opentelemetry-sdk + # via opentelemetry-semantic-conventions + # via pydantic-ai-slim + # via reboot +opentelemetry-exporter-otlp-proto-common==1.28.1 + # via opentelemetry-exporter-otlp-proto-grpc +opentelemetry-exporter-otlp-proto-grpc==1.28.1 + # via reboot +opentelemetry-instrumentation==0.49b1 + # via opentelemetry-instrumentation-grpc +opentelemetry-instrumentation-grpc==0.49b1 + # via reboot +opentelemetry-proto==1.28.1 + # via opentelemetry-exporter-otlp-proto-common + # via opentelemetry-exporter-otlp-proto-grpc +opentelemetry-sdk==1.28.1 + # via opentelemetry-exporter-otlp-proto-grpc + # via reboot +opentelemetry-semantic-conventions==0.49b1 + # via opentelemetry-instrumentation + # via opentelemetry-instrumentation-grpc + # via opentelemetry-sdk +packaging==26.2 + # via opentelemetry-instrumentation + # via reboot +pathspec==0.12.1 + # via reboot +propcache==0.4.1 + # via aiohttp + # via yarl +protobuf==5.28.3 + # via googleapis-common-protos + # via grpcio-health-checking + # via grpcio-reflection + # via grpcio-status + # via grpcio-tools + # via mypy-protobuf + # via opentelemetry-proto + # via reboot +psutil==6.0.0 + # via reboot +pycparser==3.0 + # via cffi +pydantic==2.13.3 + # via anthropic + # via fastapi + # via genai-prices + # via mcp + # via pydantic-ai-slim + # via pydantic-graph + # via pydantic-settings + # via reboot +pydantic-ai-slim==1.87.0 + # via reboot +pydantic-core==2.46.3 + # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim +pydantic-settings==2.14.0 + # via mcp +pyjwt==2.10.1 + # via mcp + # via reboot +pyprctl==0.1.3 + # via reboot +python-dateutil==2.9.0.post0 + # via kubernetes-asyncio +python-dotenv==1.2.2 + # via pydantic-settings +python-multipart==0.0.27 + # via mcp +python-ulid==3.1.0 + # via reboot +pyyaml==6.0.2 + # via kubernetes-asyncio + # via reboot +reboot==1.0.3 +referencing==0.37.0 + # via jsonschema + # via jsonschema-specifications +rpds-py==0.30.0 + # via jsonschema + # via referencing +setuptools==82.0.1 + # via grpcio-tools +six==1.17.0 + # via kubernetes-asyncio + # via python-dateutil +sniffio==1.3.1 + # via anthropic +sse-starlette==3.0.3 + # via mcp +starlette==0.46.2 + # via fastapi + # via mcp + # via reboot +tabulate==0.9.0 + # via reboot +types-protobuf==7.34.1.20260408 + # via mypy-protobuf +typing-extensions==4.15.0 + # via aiosignal + # via anthropic + # via anyio + # via exceptiongroup + # via fastapi + # via mcp + # via multidict + # via opentelemetry-sdk + # via pydantic + # via pydantic-core + # via reboot + # via referencing + # via typing-inspection + # via uvicorn +typing-inspection==0.4.2 + # via mcp + # via pydantic + # via pydantic-ai-slim + # via pydantic-graph + # via pydantic-settings +tzlocal==5.3 + # via reboot +urllib3==1.26.15 + # via kubernetes-asyncio + # via reboot +uuid7==0.1.0 +uuid7-standard==1.1.0 + # via reboot +uvicorn==0.34.0 + # via mcp + # via reboot +watchdog==6.0.0 + # via reboot +websockets==15.0.1 + # via reboot +wrapt==1.17.3 + # via deprecated + # via opentelemetry-instrumentation + # via opentelemetry-instrumentation-grpc +yarl==1.23.0 + # via aiohttp +zipp==3.23.1 + # via importlib-metadata diff --git a/reboot/examples/agent-wiki/web/index.css b/reboot/examples/agent-wiki/web/index.css new file mode 100644 index 00000000..1f47c177 --- /dev/null +++ b/reboot/examples/agent-wiki/web/index.css @@ -0,0 +1,64 @@ +:root { + /* Tron / retro-terminal palette: deep black ground, phosphor + cyan primary, hot magenta + amber for accents. */ + --color-bg: #04060d; + --color-bg-dark: #000000; + --color-border: #0f3d5c; + --color-text: #bde6ff; + --color-text-muted: #4a7a9a; + --color-green: #00ff9f; + --color-blue: #00d4ff; + --color-yellow: #ffb000; + --color-pink: #ff2a6d; + --color-purple: #b967ff; + --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, + monospace; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: var(--font-mono); + color: var(--color-text); + /* Solid color plus a thin cyan grid that stays put while the + page scrolls — evokes the Tron "Game Grid" floor. */ + background-color: var(--color-bg); + background-image: linear-gradient( + rgba(0, 212, 255, 0.05) 1px, + transparent 1px + ), + linear-gradient(90deg, rgba(0, 212, 255, 0.05) 1px, transparent 1px); + background-size: 32px 32px; + background-attachment: fixed; + position: relative; + min-height: 100vh; +} + +/* Faint horizontal scanlines overlay — CRT flavor. Pointer + events disabled so it never blocks clicks. */ +body::after { + content: ""; + position: fixed; + inset: 0; + pointer-events: none; + background: repeating-linear-gradient( + 0deg, + rgba(0, 212, 255, 0.04) 0px, + rgba(0, 212, 255, 0.04) 1px, + transparent 1px, + transparent 3px + ); + z-index: 1000; + mix-blend-mode: screen; +} + +h1, +h2, +h3 { + letter-spacing: 0.06em; + text-transform: uppercase; +} diff --git a/reboot/examples/agent-wiki/web/package-lock.json b/reboot/examples/agent-wiki/web/package-lock.json new file mode 100644 index 00000000..86b1c713 --- /dev/null +++ b/reboot/examples/agent-wiki/web/package-lock.json @@ -0,0 +1,4982 @@ +{ + "name": "agent-wiki-web", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agent-wiki-web", + "version": "0.1.0", + "dependencies": { + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", + "zod": "^3.25.0" + }, + "devDependencies": { + "@types/react": "^18.2.67", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.7.0", + "concurrently": "^9.1.2", + "typescript": "^5.9.2", + "vite": "^6.3.5", + "vite-plugin-singlefile": "^2.0.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.1.tgz", + "integrity": "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==", + "license": "(Apache-2.0 AND BSD-3-Clause)", + "peer": true + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@modelcontextprotocol/ext-apps": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", + "license": "MIT", + "workspaces": [ + "examples/*" + ], + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@reboot-dev/reboot-api": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "1.4.0", + "typescript": "5.4.5", + "zod": "^3.25.51" + }, + "peerDependencies": { + "@bufbuild/protobuf": "1.10.1" + } + }, + "node_modules/@reboot-dev/reboot-api/node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@reboot-dev/reboot-react": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", + "license": "Apache-2.0", + "dependencies": { + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", + "@scarf/scarf": "1.4.0", + "@types/uuid": "^9.0.4", + "js-sha1": "0.7.0", + "tslib": "^2.6.2", + "typescript": "4.8.4", + "uuid": "11.1.0" + }, + "peerDependencies": { + "@bufbuild/protobuf": "1.10.1", + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@reboot-dev/reboot-react/node_modules/typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/@reboot-dev/reboot-web": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", + "license": "Apache-2.0", + "dependencies": { + "@reboot-dev/reboot-api": "1.0.3", + "@scarf/scarf": "1.4.0", + "js-sha1": "0.7.0", + "lru-cache-idb": "^0.5.2", + "tslib": "^2.6.2", + "typescript": "4.8.4", + "uuid": "11.1.0" + }, + "peerDependencies": { + "@bufbuild/protobuf": "1.10.1" + } + }, + "node_modules/@reboot-dev/reboot-web/node_modules/typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", + "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hono": { + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-sha1": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/js-sha1/-/js-sha1-0.7.0.tgz", + "integrity": "sha512-oQZ1Mo7440BfLSv9TX87VNEyU52pXPVG19F9PL3gTgNt0tVxlZ8F4O6yze3CLuLx28TxotxvlyepCNaaV0ZjMw==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lru-cache-idb": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/lru-cache-idb/-/lru-cache-idb-0.5.2.tgz", + "integrity": "sha512-POlgTgPauh/q/mdh51G828HXqSI+/7KU6tKf59lppkTeTUznztGcqjeUxwjJPVOpKTgWH28DOFtbp9DpXq0zLA==", + "license": "MIT" + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-singlefile": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-2.3.3.tgz", + "integrity": "sha512-XVnGH0QzbOa8fxRSsHdCarVN1BSBXNi7uLMQYlrGRN5apdHkk62XQWRJhVever0lnfuyBkwn+kvVChdm/OoOUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">18.0.0" + }, + "peerDependencies": { + "rollup": "^4.59.0", + "vite": "^5.4.21 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/reboot/examples/agent-wiki/web/package.json b/reboot/examples/agent-wiki/web/package.json new file mode 100644 index 00000000..828969bb --- /dev/null +++ b/reboot/examples/agent-wiki/web/package.json @@ -0,0 +1,37 @@ +{ + "name": "agent-wiki-web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build:wiki_view": "vite build --mode wiki_view", + "build:page_view": "vite build --mode page_view", + "build:transcript_view": "vite build --mode transcript_view", + "build:watch:wiki_view": "vite build --mode wiki_view --watch", + "build:watch:page_view": "vite build --mode page_view --watch", + "build:watch:transcript_view": "vite build --mode transcript_view --watch", + "build": "tsc --noEmit && npm run build:wiki_view && npm run build:page_view && npm run build:transcript_view", + "build:watch": "concurrently \"npm:build:watch:*\"" + }, + "dependencies": { + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", + "zod": "^3.25.0" + }, + "devDependencies": { + "@types/react": "^18.2.67", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.7.0", + "concurrently": "^9.1.2", + "typescript": "^5.9.2", + "vite": "^6.3.5", + "vite-plugin-singlefile": "^2.0.3" + } +} diff --git a/reboot/examples/agent-wiki/web/tsconfig.app.json b/reboot/examples/agent-wiki/web/tsconfig.app.json new file mode 100644 index 00000000..910e9002 --- /dev/null +++ b/reboot/examples/agent-wiki/web/tsconfig.app.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@api/*": ["./api/*"] + } + }, + "include": ["ui"] +} diff --git a/reboot/examples/agent-wiki/web/tsconfig.json b/reboot/examples/agent-wiki/web/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/reboot/examples/agent-wiki/web/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/reboot/examples/agent-wiki/web/tsconfig.node.json b/reboot/examples/agent-wiki/web/tsconfig.node.json new file mode 100644 index 00000000..4d19ae8f --- /dev/null +++ b/reboot/examples/agent-wiki/web/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/reboot/examples/agent-wiki/web/ui/_shared/Markdown.module.css b/reboot/examples/agent-wiki/web/ui/_shared/Markdown.module.css new file mode 100644 index 00000000..279d10d4 --- /dev/null +++ b/reboot/examples/agent-wiki/web/ui/_shared/Markdown.module.css @@ -0,0 +1,178 @@ +/* Rendered markdown body. Targets child tags ReactMarkdown emits + so the author can write plain CommonMark/GFM without knowing + about CSS modules. */ +.markdown { + font-size: 14px; + line-height: 1.6; + color: var(--color-text); + background: var(--color-bg-dark); + border: 1px solid var(--color-border); + border-radius: 6px; + padding: 16px 20px; +} + +.markdown > :first-child { + margin-top: 0; +} + +.markdown > :last-child { + margin-bottom: 0; +} + +.markdown h1, +.markdown h2, +.markdown h3, +.markdown h4, +.markdown h5, +.markdown h6 { + color: var(--color-blue); + margin: 20px 0 8px; + line-height: 1.25; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.markdown h1 { + font-size: 20px; + padding-bottom: 4px; + border-bottom: 1px solid var(--color-border); +} + +.markdown h2 { + font-size: 16px; + color: var(--color-purple); +} + +.markdown h3 { + font-size: 14px; + color: var(--color-green); +} + +.markdown h4, +.markdown h5, +.markdown h6 { + font-size: 13px; + color: var(--color-text-muted); +} + +.markdown p { + margin: 8px 0; +} + +.markdown a { + color: var(--color-yellow); + text-decoration: underline; + text-decoration-color: rgba(255, 176, 0, 0.4); + text-underline-offset: 2px; +} + +.markdown a:hover { + text-decoration-color: var(--color-yellow); +} + +.markdown strong { + color: var(--color-text); + font-weight: bold; +} + +.markdown em { + color: var(--color-text); + font-style: italic; +} + +.markdown code { + font-family: var(--font-mono); + font-size: 0.92em; + background: rgba(0, 212, 255, 0.08); + border: 1px solid var(--color-border); + border-radius: 3px; + padding: 0 4px; + color: var(--color-blue); +} + +.markdown pre { + background: var(--color-bg); + border: 1px solid var(--color-border); + border-radius: 4px; + padding: 10px 12px; + overflow-x: auto; + margin: 10px 0; +} + +.markdown pre code { + background: transparent; + border: none; + padding: 0; + color: var(--color-text); + font-size: 12px; + line-height: 1.5; +} + +.markdown blockquote { + margin: 10px 0; + padding: 6px 12px; + border-left: 3px solid var(--color-pink); + color: var(--color-text-muted); + background: rgba(255, 42, 109, 0.05); +} + +.markdown ul, +.markdown ol { + padding-left: 22px; + margin: 8px 0; + list-style-position: outside; +} + +.markdown ul { + list-style: disc; +} + +.markdown ol { + list-style: decimal; +} + +.markdown li { + margin: 2px 0; +} + +.markdown li::marker { + color: var(--color-blue); +} + +.markdown hr { + border: none; + border-top: 1px solid var(--color-border); + margin: 16px 0; +} + +.markdown table { + border-collapse: collapse; + margin: 12px 0; + font-size: 13px; + width: 100%; +} + +.markdown th, +.markdown td { + border: 1px solid var(--color-border); + padding: 6px 10px; + text-align: left; +} + +.markdown th { + background: rgba(0, 212, 255, 0.08); + color: var(--color-blue); + font-weight: bold; +} + +.markdown img { + max-width: 100%; + border: 1px solid var(--color-border); + border-radius: 4px; +} + +/* GFM task list checkboxes. */ +.markdown input[type="checkbox"] { + accent-color: var(--color-blue); + margin-right: 4px; +} diff --git a/reboot/examples/agent-wiki/web/ui/_shared/Markdown.tsx b/reboot/examples/agent-wiki/web/ui/_shared/Markdown.tsx new file mode 100644 index 00000000..8e59fed7 --- /dev/null +++ b/reboot/examples/agent-wiki/web/ui/_shared/Markdown.tsx @@ -0,0 +1,14 @@ +import { type FC } from "react"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import css from "./Markdown.module.css"; + +interface MarkdownProps { + children: string; +} + +export const Markdown: FC = ({ children }) => ( +
+ {children} +
+); diff --git a/reboot/examples/agent-wiki/web/ui/page_view/App.module.css b/reboot/examples/agent-wiki/web/ui/page_view/App.module.css new file mode 100644 index 00000000..582c0303 --- /dev/null +++ b/reboot/examples/agent-wiki/web/ui/page_view/App.module.css @@ -0,0 +1,37 @@ +.container { + background: var(--color-bg); + color: var(--color-text); + font-family: var(--font-mono); + padding: 24px; + display: flex; + flex-direction: column; + gap: 18px; + max-width: 720px; + margin: 0 auto; +} + +.header { + border-bottom: 1px solid var(--color-border); + padding-bottom: 10px; +} + +.title { + font-size: 22px; + font-weight: bold; + color: var(--color-green); +} + +.empty { + font-size: 13px; + color: var(--color-text-muted); + padding: 16px; + border: 1px dashed var(--color-border); + border-radius: 6px; + text-align: center; +} + +.loading { + color: var(--color-text-muted); + font-size: 12px; + padding: 24px; +} diff --git a/reboot/examples/agent-wiki/web/ui/page_view/App.tsx b/reboot/examples/agent-wiki/web/ui/page_view/App.tsx new file mode 100644 index 00000000..47a72939 --- /dev/null +++ b/reboot/examples/agent-wiki/web/ui/page_view/App.tsx @@ -0,0 +1,32 @@ +import { type FC } from "react"; +import { usePage } from "@api/agent_wiki/v1/wiki_rbt_react"; +import { Markdown } from "../_shared/Markdown"; +import css from "./App.module.css"; + +export const PageView: FC = () => { + const page = usePage(); + const { response, isLoading } = page.useGet(); + + if (isLoading && response === undefined) { + return ( +
+
loading...
+
+ ); + } + + const content = response?.content ?? ""; + + return ( +
+
+

{response?.title || "Untitled Page"}

+
+ {content ? ( + {content} + ) : ( +
This page has no content yet.
+ )} +
+ ); +}; diff --git a/reboot/examples/agent-wiki/web/ui/page_view/index.html b/reboot/examples/agent-wiki/web/ui/page_view/index.html new file mode 100644 index 00000000..f0f5ba1a --- /dev/null +++ b/reboot/examples/agent-wiki/web/ui/page_view/index.html @@ -0,0 +1,12 @@ + + + + + + Page + + +
+ + + diff --git a/reboot/examples/agent-wiki/web/ui/page_view/main.tsx b/reboot/examples/agent-wiki/web/ui/page_view/main.tsx new file mode 100644 index 00000000..cd7baa65 --- /dev/null +++ b/reboot/examples/agent-wiki/web/ui/page_view/main.tsx @@ -0,0 +1,13 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { RebootClientProvider } from "@reboot-dev/reboot-react"; +import { PageView } from "./App"; +import "../../index.css"; + +createRoot(document.getElementById("root")!).render( + + + + + +); diff --git a/reboot/examples/agent-wiki/web/ui/transcript_view/App.module.css b/reboot/examples/agent-wiki/web/ui/transcript_view/App.module.css new file mode 100644 index 00000000..e338c539 --- /dev/null +++ b/reboot/examples/agent-wiki/web/ui/transcript_view/App.module.css @@ -0,0 +1,75 @@ +.container { + background: var(--color-bg); + color: var(--color-text); + font-family: var(--font-mono); + padding: 24px; + display: flex; + flex-direction: column; + gap: 14px; + max-width: 720px; + margin: 0 auto; +} + +.header { + border-bottom: 1px solid var(--color-border); + padding-bottom: 10px; +} + +.title { + font-size: 22px; + font-weight: bold; + color: var(--color-pink); +} + +.messageList { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 10px; + max-height: 640px; + overflow-y: auto; +} + +.message { + background: var(--color-bg-dark); + border: 1px solid var(--color-border); + border-radius: 6px; + padding: 10px 12px; + display: flex; + flex-direction: column; + gap: 6px; +} + +.role { + font-size: 11px; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--color-purple); +} + +.content { + margin: 0; + font-size: 13px; + line-height: 1.5; + color: var(--color-text); + white-space: pre-wrap; + font-family: var(--font-mono); +} + +.empty { + font-size: 13px; + color: var(--color-text-muted); + padding: 16px; + border: 1px dashed var(--color-border); + border-radius: 6px; + text-align: center; +} + +.loading { + color: var(--color-text-muted); + font-size: 12px; + padding: 24px; +} diff --git a/reboot/examples/agent-wiki/web/ui/transcript_view/App.tsx b/reboot/examples/agent-wiki/web/ui/transcript_view/App.tsx new file mode 100644 index 00000000..ee54c889 --- /dev/null +++ b/reboot/examples/agent-wiki/web/ui/transcript_view/App.tsx @@ -0,0 +1,40 @@ +import { type FC } from "react"; +import { useTranscript } from "@api/agent_wiki/v1/wiki_rbt_react"; +import css from "./App.module.css"; + +export const TranscriptView: FC = () => { + const transcript = useTranscript(); + const { response, isLoading } = transcript.useGet(); + + if (isLoading && response === undefined) { + return ( +
+
loading...
+
+ ); + } + + const messages = response?.messages ?? []; + + return ( +
+
+

Transcript

+
+ {messages.length === 0 ? ( +
+ Transcript is empty. Ask the AI to add the conversation. +
+ ) : ( +
    + {messages.map((message, index) => ( +
  1. +
    {message.role}
    +
    {message.content}
    +
  2. + ))} +
+ )} +
+ ); +}; diff --git a/reboot/examples/agent-wiki/web/ui/transcript_view/index.html b/reboot/examples/agent-wiki/web/ui/transcript_view/index.html new file mode 100644 index 00000000..f822de9d --- /dev/null +++ b/reboot/examples/agent-wiki/web/ui/transcript_view/index.html @@ -0,0 +1,12 @@ + + + + + + Transcript + + +
+ + + diff --git a/reboot/examples/agent-wiki/web/ui/transcript_view/main.tsx b/reboot/examples/agent-wiki/web/ui/transcript_view/main.tsx new file mode 100644 index 00000000..fc7c8230 --- /dev/null +++ b/reboot/examples/agent-wiki/web/ui/transcript_view/main.tsx @@ -0,0 +1,13 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { RebootClientProvider } from "@reboot-dev/reboot-react"; +import { TranscriptView } from "./App"; +import "../../index.css"; + +createRoot(document.getElementById("root")!).render( + + + + + +); diff --git a/reboot/examples/agent-wiki/web/ui/wiki_view/App.module.css b/reboot/examples/agent-wiki/web/ui/wiki_view/App.module.css new file mode 100644 index 00000000..d76fbf85 --- /dev/null +++ b/reboot/examples/agent-wiki/web/ui/wiki_view/App.module.css @@ -0,0 +1,45 @@ +.container { + background: var(--color-bg); + color: var(--color-text); + font-family: var(--font-mono); + padding: 24px; + display: flex; + flex-direction: column; + gap: 20px; + max-width: 720px; + margin: 0 auto; +} + +.header { + border-bottom: 1px solid var(--color-border); + padding-bottom: 12px; + display: flex; + flex-direction: column; + gap: 4px; +} + +.title { + font-size: 22px; + font-weight: bold; + color: var(--color-blue); +} + +.description { + font-size: 13px; + color: var(--color-text-muted); +} + +.empty { + font-size: 13px; + color: var(--color-text-muted); + padding: 16px; + border: 1px dashed var(--color-border); + border-radius: 6px; + text-align: center; +} + +.loading { + color: var(--color-text-muted); + font-size: 12px; + padding: 24px; +} diff --git a/reboot/examples/agent-wiki/web/ui/wiki_view/App.tsx b/reboot/examples/agent-wiki/web/ui/wiki_view/App.tsx new file mode 100644 index 00000000..b4227c86 --- /dev/null +++ b/reboot/examples/agent-wiki/web/ui/wiki_view/App.tsx @@ -0,0 +1,35 @@ +import { type FC } from "react"; +import { useWiki } from "@api/agent_wiki/v1/wiki_rbt_react"; +import { Markdown } from "../_shared/Markdown"; +import css from "./App.module.css"; + +export const WikiView: FC = () => { + const wiki = useWiki(); + const { response, isLoading } = wiki.useGet(); + + if (isLoading && response === undefined) { + return ( +
+
loading...
+
+ ); + } + + const content = response?.content ?? ""; + + return ( +
+
+

{response?.name || "Unnamed Wiki"}

+ {response?.description && ( +

{response.description}

+ )} +
+ {content ? ( + {content} + ) : ( +
This wiki has no content yet.
+ )} +
+ ); +}; diff --git a/reboot/examples/agent-wiki/web/ui/wiki_view/index.html b/reboot/examples/agent-wiki/web/ui/wiki_view/index.html new file mode 100644 index 00000000..ce700dd9 --- /dev/null +++ b/reboot/examples/agent-wiki/web/ui/wiki_view/index.html @@ -0,0 +1,12 @@ + + + + + + Wiki + + +
+ + + diff --git a/reboot/examples/agent-wiki/web/ui/wiki_view/main.tsx b/reboot/examples/agent-wiki/web/ui/wiki_view/main.tsx new file mode 100644 index 00000000..88912346 --- /dev/null +++ b/reboot/examples/agent-wiki/web/ui/wiki_view/main.tsx @@ -0,0 +1,13 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { RebootClientProvider } from "@reboot-dev/reboot-react"; +import { WikiView } from "./App"; +import "../../index.css"; + +createRoot(document.getElementById("root")!).render( + + + + + +); diff --git a/reboot/examples/agent-wiki/web/vite.config.ts b/reboot/examples/agent-wiki/web/vite.config.ts new file mode 100644 index 00000000..ab3670df --- /dev/null +++ b/reboot/examples/agent-wiki/web/vite.config.ts @@ -0,0 +1,70 @@ +// Vite configuration for Reboot UIs. +import fs from "fs"; +import path from "path"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; +import { viteSingleFile } from "vite-plugin-singlefile"; + +// Auto-discover UIs from ui/ directory. +const uiDir = path.resolve(__dirname, "ui"); +const uis: Record = + Object.fromEntries( + fs + .readdirSync(uiDir) + .filter((d) => fs.existsSync(path.join(uiDir, d, "index.html"))) + .map((name) => [ + name, + { input: `ui/${name}/index.html`, output: `${name}.html` }, + ]) + ); + +export default defineConfig(({ command, mode }) => { + const resolve = { + alias: { + "@api": path.resolve(__dirname, "./api"), + }, + dedupe: ["react", "react-dom", "zod"], + }; + + if (command === "serve") { + const port = parseInt(process.env.RBT_VITE_PORT || "4444", 10); + + return { + plugins: [react()], + root: ".", + resolve, + base: "/__/web/", + server: { + port, + strictPort: true, + host: true, + allowedHosts: true, + }, + }; + } + + const ui = uis[mode]; + if (!ui) { + const valid = Object.keys(uis).join(", "); + throw new Error(`Unknown UI: ${mode}. Use --mode with: ${valid}`); + } + + return { + plugins: [react(), viteSingleFile()], + build: { + outDir: "dist", + emptyOutDir: false, + assetsInlineLimit: 100000000, + cssCodeSplit: false, + rollupOptions: { + input: ui.input, + output: { + inlineDynamicImports: true, + entryFileNames: ui.output.replace(".html", ".js"), + assetFileNames: ui.output.replace(".html", ".[ext]"), + }, + }, + }, + resolve, + }; +}); diff --git a/reboot/examples/ai-chat-counter-dashboard/.rbtrc b/reboot/examples/ai-chat-counter-dashboard/.rbtrc index 497488e3..36022fc4 100644 --- a/reboot/examples/ai-chat-counter-dashboard/.rbtrc +++ b/reboot/examples/ai-chat-counter-dashboard/.rbtrc @@ -15,7 +15,7 @@ dev run --watch=backend/\*_/_.py dev run --python # Save state between restarts. -dev run --name=ai-chat-counter-dashboard +dev run --application-name=ai-chat-counter-dashboard # Entrypoint. dev run --application=backend/src/main.py @@ -33,4 +33,4 @@ dev run:hmr --mcp-frontend-host=http://localhost:4444 dev run:dist --mcp-frontend-host="" # When expunging, expunge that state we've saved. -dev expunge --name=ai-chat-counter-dashboard +dev expunge --application-name=ai-chat-counter-dashboard diff --git a/reboot/examples/ai-chat-counter-dashboard/README.md b/reboot/examples/ai-chat-counter-dashboard/README.md index 45ca43df..0b65d96d 100644 --- a/reboot/examples/ai-chat-counter-dashboard/README.md +++ b/reboot/examples/ai-chat-counter-dashboard/README.md @@ -29,7 +29,7 @@ The project includes `mcp_servers.json` for testing with MCPJam Inspector: ```bash # In another terminal, run MCPJam: -npx @mcpjam/inspector@v2.0.18 --config mcp_servers.json --server counter-server +npx @mcpjam/inspector@2.4.0 --config mcp_servers.json --server counter-server ``` This opens a browser-based inspector where you can test tools. diff --git a/reboot/examples/ai-chat-counter-dashboard/api/ai_chat_counter/v1/counter.py b/reboot/examples/ai-chat-counter-dashboard/api/ai_chat_counter/v1/counter.py index 926bfbf0..b3b780dd 100644 --- a/reboot/examples/ai-chat-counter-dashboard/api/ai_chat_counter/v1/counter.py +++ b/reboot/examples/ai-chat-counter-dashboard/api/ai_chat_counter/v1/counter.py @@ -68,6 +68,7 @@ class DashboardConfig(Model): "the `counter_id`, which is not " "human-readable but should be passed to " "future tool calls that need it.", + mcp=Tool(), ), list_counters=Reader( request=None, @@ -77,6 +78,7 @@ class DashboardConfig(Model): "description for each. The `counter_id` " "is not human-readable, but use it when " "calling tools that take a `counter_id`.", + mcp=Tool(), ), ), ), @@ -102,6 +104,7 @@ class DashboardConfig(Model): request=CreateCounterRequest, response=None, factory=True, + mcp=None, ), get=Reader( request=None, @@ -120,6 +123,7 @@ class DashboardConfig(Model): description=Reader( request=None, response=DescriptionResponse, + mcp=None, ), ), ), diff --git a/reboot/examples/ai-chat-counter-dashboard/backend/src/servicers/counter.py b/reboot/examples/ai-chat-counter-dashboard/backend/src/servicers/counter.py index 1c1d3bd5..9c5cb8f7 100644 --- a/reboot/examples/ai-chat-counter-dashboard/backend/src/servicers/counter.py +++ b/reboot/examples/ai-chat-counter-dashboard/backend/src/servicers/counter.py @@ -1,4 +1,5 @@ from ai_chat_counter.v1.counter import ( + CounterEntry, CreateCounterRequest, CreateCounterResponse, ListCountersResponse, @@ -38,7 +39,7 @@ async def list_counters( for counter_id in self.state.counter_ids: response = await Counter.ref(counter_id).description(context) counters.append( - User.CounterEntry( + CounterEntry( counter_id=counter_id, description=response.description, ) diff --git a/reboot/examples/ai-chat-counter-dashboard/mcp_servers.json b/reboot/examples/ai-chat-counter-dashboard/mcp_servers.json index 3529238f..7ea8c989 100644 --- a/reboot/examples/ai-chat-counter-dashboard/mcp_servers.json +++ b/reboot/examples/ai-chat-counter-dashboard/mcp_servers.json @@ -1,9 +1,8 @@ { "mcpServers": { "counter-server": { - "type": "streamable-http", "url": "http://localhost:9991/mcp", - "auth": "oauth" + "useOAuth": true } } } diff --git a/reboot/examples/ai-chat-counter-dashboard/pyproject.toml b/reboot/examples/ai-chat-counter-dashboard/pyproject.toml index b8335661..a071b65d 100644 --- a/reboot/examples/ai-chat-counter-dashboard/pyproject.toml +++ b/reboot/examples/ai-chat-counter-dashboard/pyproject.toml @@ -6,14 +6,14 @@ dependencies = [ "httpx>=0.27,<1.0", "uuid7>=0.1.0", "anyio>=4.0.0", - "reboot==0.46.0", + "reboot==1.0.3", ] [tool.rye] dev-dependencies = [ "mypy==1.18.1", "types-protobuf>=4.24.0.20240129", - "reboot==0.46.0", + "reboot==1.0.3", ] # This project only uses `rye` to provide `python` and its dependencies. diff --git a/reboot/examples/ai-chat-counter-dashboard/requirements-dev.lock b/reboot/examples/ai-chat-counter-dashboard/requirements-dev.lock index 0d48cc33..a1b08e35 100644 --- a/reboot/examples/ai-chat-counter-dashboard/requirements-dev.lock +++ b/reboot/examples/ai-chat-counter-dashboard/requirements-dev.lock @@ -52,15 +52,20 @@ deprecated==1.3.1 # via opentelemetry-semantic-conventions exceptiongroup==1.3.1 # via anyio + # via pydantic-ai-slim fastapi==0.115.12 # via reboot frozenlist==1.8.0 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -89,7 +94,10 @@ hpack==4.1.0 httpcore==1.0.9 # via httpx httpx==0.28.1 + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 @@ -111,9 +119,11 @@ jsonschema-specifications==2025.9.1 # via jsonschema kubernetes-asyncio==31.1.0 # via reboot +logfire-api==4.32.1 + # via pydantic-graph markupsafe==3.0.3 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot multidict==6.7.0 # via aiohttp @@ -129,6 +139,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -172,11 +183,18 @@ pycparser==3.0 # via cffi pydantic==2.12.5 # via fastapi + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot +pydantic-ai-slim==1.87.0 + # via reboot pydantic-core==2.41.5 # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim pydantic-settings==2.12.0 # via mcp pyjwt==2.10.1 @@ -195,7 +213,7 @@ python-ulid==3.1.0 pyyaml==6.0.2 # via kubernetes-asyncio # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications @@ -230,12 +248,15 @@ typing-extensions==4.15.0 # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via typing-inspection # via uvicorn typing-inspection==0.4.2 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot diff --git a/reboot/examples/ai-chat-counter-dashboard/requirements.lock b/reboot/examples/ai-chat-counter-dashboard/requirements.lock index 1d38fdeb..82a7128e 100644 --- a/reboot/examples/ai-chat-counter-dashboard/requirements.lock +++ b/reboot/examples/ai-chat-counter-dashboard/requirements.lock @@ -52,15 +52,20 @@ deprecated==1.3.1 # via opentelemetry-semantic-conventions exceptiongroup==1.3.1 # via anyio + # via pydantic-ai-slim fastapi==0.115.12 # via reboot frozenlist==1.8.0 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -89,7 +94,10 @@ hpack==4.1.0 httpcore==1.0.9 # via httpx httpx==0.28.1 + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 @@ -111,9 +119,11 @@ jsonschema-specifications==2025.9.1 # via jsonschema kubernetes-asyncio==31.1.0 # via reboot +logfire-api==4.32.1 + # via pydantic-graph markupsafe==3.0.3 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot multidict==6.7.1 # via aiohttp @@ -126,6 +136,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -168,11 +179,18 @@ pycparser==3.0 # via cffi pydantic==2.12.5 # via fastapi + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot +pydantic-ai-slim==1.87.0 + # via reboot pydantic-core==2.41.5 # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim pydantic-settings==2.13.1 # via mcp pyjwt==2.10.1 @@ -191,7 +209,7 @@ python-ulid==3.1.0 pyyaml==6.0.2 # via kubernetes-asyncio # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications @@ -223,12 +241,15 @@ typing-extensions==4.15.0 # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via typing-inspection # via uvicorn typing-inspection==0.4.2 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot diff --git a/reboot/examples/ai-chat-counter-dashboard/web/package-lock.json b/reboot/examples/ai-chat-counter-dashboard/web/package-lock.json index bd1f749d..6b7776e8 100644 --- a/reboot/examples/ai-chat-counter-dashboard/web/package-lock.json +++ b/reboot/examples/ai-chat-counter-dashboard/web/package-lock.json @@ -8,10 +8,10 @@ "name": "ai-chat-counter-dashboard-web", "version": "0.1.0", "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-react": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", "zod": "^3.25.0" @@ -27,9 +27,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { @@ -42,9 +42,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", - "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, "license": "MIT", "engines": { @@ -52,21 +52,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", - "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -83,14 +83,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", - "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -199,27 +199,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -276,18 +276,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -295,9 +295,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { @@ -758,9 +758,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", - "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -820,15 +820,18 @@ } }, "node_modules/@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "license": "MIT", "workspaces": [ "examples/*" ], + "engines": { + "node": ">=20" + }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" @@ -843,9 +846,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -883,9 +886,9 @@ } }, "node_modules/@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0", @@ -910,15 +913,15 @@ } }, "node_modules/@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", "license": "Apache-2.0", "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -946,12 +949,12 @@ } }, "node_modules/@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -984,9 +987,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz", - "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", "cpu": [ "arm" ], @@ -998,9 +1001,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz", - "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", "cpu": [ "arm64" ], @@ -1012,9 +1015,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz", - "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", "cpu": [ "arm64" ], @@ -1026,9 +1029,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz", - "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", "cpu": [ "x64" ], @@ -1040,9 +1043,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz", - "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", "cpu": [ "arm64" ], @@ -1054,9 +1057,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz", - "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", "cpu": [ "x64" ], @@ -1068,9 +1071,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz", - "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", "cpu": [ "arm" ], @@ -1082,9 +1085,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz", - "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", "cpu": [ "arm" ], @@ -1096,9 +1099,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz", - "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", "cpu": [ "arm64" ], @@ -1110,9 +1113,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz", - "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", "cpu": [ "arm64" ], @@ -1124,9 +1127,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz", - "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", "cpu": [ "loong64" ], @@ -1138,9 +1141,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz", - "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", "cpu": [ "loong64" ], @@ -1152,9 +1155,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz", - "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", "cpu": [ "ppc64" ], @@ -1166,9 +1169,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz", - "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", "cpu": [ "ppc64" ], @@ -1180,9 +1183,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz", - "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", "cpu": [ "riscv64" ], @@ -1194,9 +1197,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz", - "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", "cpu": [ "riscv64" ], @@ -1208,9 +1211,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz", - "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", "cpu": [ "s390x" ], @@ -1222,9 +1225,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", - "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", "cpu": [ "x64" ], @@ -1236,9 +1239,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz", - "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", "cpu": [ "x64" ], @@ -1250,9 +1253,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz", - "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", "cpu": [ "x64" ], @@ -1264,9 +1267,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz", - "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", "cpu": [ "arm64" ], @@ -1278,9 +1281,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz", - "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", "cpu": [ "arm64" ], @@ -1292,9 +1295,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz", - "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", "cpu": [ "ia32" ], @@ -1306,9 +1309,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz", - "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", "cpu": [ "x64" ], @@ -1320,9 +1323,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", - "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", "cpu": [ "x64" ], @@ -1400,9 +1403,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "dev": true, "license": "MIT", "dependencies": { @@ -1461,9 +1464,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1520,13 +1523,16 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.9.18", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz", - "integrity": "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==", + "version": "2.10.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", + "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", "dev": true, "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/body-parser": { @@ -1567,9 +1573,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -1587,11 +1593,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -1639,9 +1645,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001766", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", - "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", "dev": true, "funding": [ { @@ -1750,9 +1756,9 @@ } }, "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "license": "MIT", "engines": { "node": ">=18" @@ -1881,9 +1887,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.279", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.279.tgz", - "integrity": "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==", + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", "dev": true, "license": "ISC" }, @@ -2013,9 +2019,9 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -2065,9 +2071,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "license": "MIT", "dependencies": { "ip-address": "10.1.0" @@ -2290,9 +2296,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -2302,9 +2308,9 @@ } }, "node_modules/hono": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.6.tgz", - "integrity": "sha512-ofIiiHyl34SV6AuhE3YT2mhO5HRWokce+eUYE82TsP6z0/H3JeJcjVWEMSIAiw2QkjDOEpES/lYsg8eEbsLtdw==", + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -2403,9 +2409,9 @@ "license": "ISC" }, "node_modules/jose": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", - "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -2534,9 +2540,9 @@ } }, "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -2606,9 +2612,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", "dev": true, "license": "MIT" }, @@ -2673,9 +2679,9 @@ } }, "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", "funding": { "type": "opencollective", @@ -2690,9 +2696,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -2712,9 +2718,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", "dev": true, "funding": [ { @@ -2754,9 +2760,9 @@ } }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -2847,9 +2853,9 @@ } }, "node_modules/rollup": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", - "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2863,31 +2869,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.56.0", - "@rollup/rollup-android-arm64": "4.56.0", - "@rollup/rollup-darwin-arm64": "4.56.0", - "@rollup/rollup-darwin-x64": "4.56.0", - "@rollup/rollup-freebsd-arm64": "4.56.0", - "@rollup/rollup-freebsd-x64": "4.56.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", - "@rollup/rollup-linux-arm-musleabihf": "4.56.0", - "@rollup/rollup-linux-arm64-gnu": "4.56.0", - "@rollup/rollup-linux-arm64-musl": "4.56.0", - "@rollup/rollup-linux-loong64-gnu": "4.56.0", - "@rollup/rollup-linux-loong64-musl": "4.56.0", - "@rollup/rollup-linux-ppc64-gnu": "4.56.0", - "@rollup/rollup-linux-ppc64-musl": "4.56.0", - "@rollup/rollup-linux-riscv64-gnu": "4.56.0", - "@rollup/rollup-linux-riscv64-musl": "4.56.0", - "@rollup/rollup-linux-s390x-gnu": "4.56.0", - "@rollup/rollup-linux-x64-gnu": "4.56.0", - "@rollup/rollup-linux-x64-musl": "4.56.0", - "@rollup/rollup-openbsd-x64": "4.56.0", - "@rollup/rollup-openharmony-arm64": "4.56.0", - "@rollup/rollup-win32-arm64-msvc": "4.56.0", - "@rollup/rollup-win32-ia32-msvc": "4.56.0", - "@rollup/rollup-win32-x64-gnu": "4.56.0", - "@rollup/rollup-win32-x64-msvc": "4.56.0", + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", "fsevents": "~2.3.2" } }, @@ -3047,13 +3053,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -3163,14 +3169,14 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -3308,9 +3314,9 @@ } }, "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3383,9 +3389,9 @@ } }, "node_modules/vite-plugin-singlefile": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-2.3.0.tgz", - "integrity": "sha512-DAcHzYypM0CasNLSz/WG0VdKOCxGHErfrjOoyIPiNxTPTGmO6rRD/te93n1YL/s+miXq66ipF1brMBikf99c6A==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-2.3.3.tgz", + "integrity": "sha512-XVnGH0QzbOa8fxRSsHdCarVN1BSBXNi7uLMQYlrGRN5apdHkk62XQWRJhVever0lnfuyBkwn+kvVChdm/OoOUg==", "dev": true, "license": "MIT", "dependencies": { @@ -3395,8 +3401,13 @@ "node": ">18.0.0" }, "peerDependencies": { - "rollup": "^4.44.1", - "vite": "^5.4.11 || ^6.0.0 || ^7.0.0" + "rollup": "^4.59.0", + "vite": "^5.4.21 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } } }, "node_modules/which": { @@ -3494,12 +3505,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", "peerDependencies": { - "zod": "^3.25 || ^4" + "zod": "^3.25.28 || ^4" } } } diff --git a/reboot/examples/ai-chat-counter-dashboard/web/package.json b/reboot/examples/ai-chat-counter-dashboard/web/package.json index be009499..b8ba8c70 100644 --- a/reboot/examples/ai-chat-counter-dashboard/web/package.json +++ b/reboot/examples/ai-chat-counter-dashboard/web/package.json @@ -13,10 +13,10 @@ "build:watch": "concurrently \"npm:build:watch:*\"" }, "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-react": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-react": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", "zod": "^3.25.0" diff --git a/reboot/examples/ai-chat-counter/.rbtrc b/reboot/examples/ai-chat-counter/.rbtrc index 88d99445..a1da3e4d 100644 --- a/reboot/examples/ai-chat-counter/.rbtrc +++ b/reboot/examples/ai-chat-counter/.rbtrc @@ -17,7 +17,7 @@ dev run --watch=web/dist/**/*.html dev run --python # Save state between restarts. -dev run --name=ai-chat-counter +dev run --application-name=ai-chat-counter # Entrypoint. dev run --application=backend/src/main.py @@ -35,4 +35,31 @@ dev run:hmr --mcp-frontend-host=http://localhost:4444 dev run:dist --mcp-frontend-host="" # When expunging, expunge that state we've saved. -dev expunge --name=ai-chat-counter +dev expunge --application-name=ai-chat-counter + +# Production: tell `rbt serve` this is a Python application and where the +# entrypoint lives. The Dockerfile's `CMD ["rbt", "serve", "run"]` picks +# these up. +serve run --python +serve run --application=backend/src/main.py + +# Make both the generated code directory (`backend/api/`) and the +# schema source directory (`api/`) importable. `rbt serve run` only +# auto-adds the `--python=` directory to `PYTHONPATH`; for apps that +# define their schemas in Pydantic modules under `api/` (and import +# from them directly, e.g. `from ai_chat_counter.v1.counter import +# CreateCounterRequest`), `api/` needs to be on the path too. Setting +# `--env=PYTHONPATH=...` overrides the auto-set, so include both. +# (`rbt dev run` does this automatically; `rbt serve run` does not.) +serve run --env=PYTHONPATH=backend/api:api + +# Reboot Cloud terminates TLS at the external load balancer, so the app +# itself only exposes a non-TLS port. +serve run --tls=external + +# Application name, used by `rbt cloud {up,down,logs}` and `rbt serve run` +# to identify this app. +serve run --application-name=ai-chat-counter +cloud up --application-name=ai-chat-counter +cloud down --application-name=ai-chat-counter --expunge +cloud logs --application-name=ai-chat-counter diff --git a/reboot/examples/ai-chat-counter/Dockerfile b/reboot/examples/ai-chat-counter/Dockerfile new file mode 100644 index 00000000..c96449ef --- /dev/null +++ b/reboot/examples/ai-chat-counter/Dockerfile @@ -0,0 +1,32 @@ +FROM ghcr.io/reboot-dev/reboot-base:1.0.3 + +WORKDIR /app + +# First ONLY copy and install the requirements, so that changes outside +# `requirements.txt` don't force a re-install of all dependencies. +# +# Note that this will install the Reboot library and CLI. +COPY requirements.lock requirements.txt +RUN pip install -r requirements.txt + +# Copy the API definition, `.rbtrc`, and the web frontend source. We +# copy `web/` BEFORE running `rbt generate` so that the freshly +# generated `web/api/` (React bindings) doesn't get overwritten by a +# stale local copy. +COPY api/ api/ +COPY .rbtrc .rbtrc +COPY web/ web/ + +# Run the Reboot code generators. We did copy all of `api/`, possibly +# including generated code, but it's not certain that `rbt generate` was +# run in that folder before this build was started. +RUN rbt generate + +# Build the React UIs into `web/dist/`. For MCP apps the Reboot app +# itself is also the static web server, so it needs these static assets. +RUN cd web && npm ci && npm run build + +# Now copy the rest of the source code. +COPY backend/src/ backend/src/ + +CMD ["rbt", "serve", "run"] diff --git a/reboot/examples/ai-chat-counter/README.md b/reboot/examples/ai-chat-counter/README.md index 422abc87..373299ff 100644 --- a/reboot/examples/ai-chat-counter/README.md +++ b/reboot/examples/ai-chat-counter/README.md @@ -28,7 +28,7 @@ The project includes `mcp_servers.json` for testing with MCPJam Inspector: ```bash # In another terminal, run MCPJam: -npx @mcpjam/inspector@v2.0.18 --config mcp_servers.json --server counter-server +npx @mcpjam/inspector@2.4.0 --config mcp_servers.json --server counter-server ``` This opens a browser-based inspector where you can test tools. diff --git a/reboot/examples/ai-chat-counter/api/ai_chat_counter/v1/counter.py b/reboot/examples/ai-chat-counter/api/ai_chat_counter/v1/counter.py index d219f313..a8367b4b 100644 --- a/reboot/examples/ai-chat-counter/api/ai_chat_counter/v1/counter.py +++ b/reboot/examples/ai-chat-counter/api/ai_chat_counter/v1/counter.py @@ -64,6 +64,7 @@ class IncrementRequest(Model): "the `counter_id`, which is not " "human-readable but should be passed to " "future tool calls that need it.", + mcp=Tool(), ), list_counters=Reader( request=None, @@ -73,6 +74,7 @@ class IncrementRequest(Model): "description for each. The `counter_id` " "is not human-readable, but use it when " "calling tools that take a `counter_id`.", + mcp=Tool(), ), ), ), @@ -90,6 +92,7 @@ class IncrementRequest(Model): request=CreateCounterRequest, response=None, factory=True, + mcp=None, ), get=Reader( request=None, @@ -108,6 +111,7 @@ class IncrementRequest(Model): description=Reader( request=None, response=DescriptionResponse, + mcp=None, ), ), ), diff --git a/reboot/examples/ai-chat-counter/backend/src/servicers/counter.py b/reboot/examples/ai-chat-counter/backend/src/servicers/counter.py index ecdca828..4ebebcc8 100644 --- a/reboot/examples/ai-chat-counter/backend/src/servicers/counter.py +++ b/reboot/examples/ai-chat-counter/backend/src/servicers/counter.py @@ -1,5 +1,6 @@ # backend/src/servicers/counter.py from ai_chat_counter.v1.counter import ( + CounterEntry, CreateCounterRequest, CreateCounterResponse, ListCountersResponse, @@ -39,7 +40,7 @@ async def list_counters( for counter_id in self.state.counter_ids: response = await Counter.ref(counter_id).description(context) counters.append( - User.CounterEntry( + CounterEntry( counter_id=counter_id, description=response.description, ) diff --git a/reboot/examples/ai-chat-counter/mcp_servers.json b/reboot/examples/ai-chat-counter/mcp_servers.json index 3529238f..7ea8c989 100644 --- a/reboot/examples/ai-chat-counter/mcp_servers.json +++ b/reboot/examples/ai-chat-counter/mcp_servers.json @@ -1,9 +1,8 @@ { "mcpServers": { "counter-server": { - "type": "streamable-http", "url": "http://localhost:9991/mcp", - "auth": "oauth" + "useOAuth": true } } } diff --git a/reboot/examples/ai-chat-counter/pyproject.toml b/reboot/examples/ai-chat-counter/pyproject.toml index 4d220430..49bc0651 100644 --- a/reboot/examples/ai-chat-counter/pyproject.toml +++ b/reboot/examples/ai-chat-counter/pyproject.toml @@ -6,14 +6,14 @@ dependencies = [ "httpx>=0.27,<1.0", "uuid7>=0.1.0", "anyio>=4.0.0", - "reboot==0.46.0", + "reboot==1.0.3", ] [tool.rye] dev-dependencies = [ "mypy==1.18.1", "types-protobuf>=4.24.0.20240129", - "reboot==0.46.0", + "reboot==1.0.3", ] # This project only uses `rye` to provide `python` and its dependencies. diff --git a/reboot/examples/ai-chat-counter/requirements-dev.lock b/reboot/examples/ai-chat-counter/requirements-dev.lock index 0d48cc33..3e41f61c 100644 --- a/reboot/examples/ai-chat-counter/requirements-dev.lock +++ b/reboot/examples/ai-chat-counter/requirements-dev.lock @@ -18,7 +18,7 @@ aiosignal==1.4.0 # via aiohttp annotated-types==0.7.0 # via pydantic -anyio==4.12.1 +anyio==4.13.0 # via httpx # via mcp # via reboot @@ -26,20 +26,20 @@ anyio==4.12.1 # via starlette async-timeout==5.0.1 # via aiohttp -attrs==25.4.0 +attrs==26.1.0 # via aiohttp # via jsonschema # via referencing bitarray==3.8.0 # via reboot -certifi==2026.1.4 +certifi==2026.4.22 # via httpcore # via httpx # via kubernetes-asyncio cffi==1.17.1 # via cryptography # via reboot -click==8.3.1 +click==8.3.3 # via uvicorn colorama==0.4.6 # via reboot @@ -52,15 +52,20 @@ deprecated==1.3.1 # via opentelemetry-semantic-conventions exceptiongroup==1.3.1 # via anyio + # via pydantic-ai-slim fastapi==0.115.12 # via reboot frozenlist==1.8.0 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -89,12 +94,15 @@ hpack==4.1.0 httpcore==1.0.9 # via httpx httpx==0.28.1 + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 # via h2 -idna==3.11 +idna==3.13 # via anyio # via httpx # via yarl @@ -111,11 +119,13 @@ jsonschema-specifications==2025.9.1 # via jsonschema kubernetes-asyncio==31.1.0 # via reboot +logfire-api==4.32.1 + # via pydantic-graph markupsafe==3.0.3 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot -multidict==6.7.0 +multidict==6.7.1 # via aiohttp # via yarl mypy==1.18.1 @@ -129,6 +139,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -148,7 +159,7 @@ opentelemetry-semantic-conventions==0.49b1 # via opentelemetry-instrumentation # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk -packaging==26.0 +packaging==26.2 # via opentelemetry-instrumentation # via reboot pathspec==0.12.1 @@ -170,14 +181,21 @@ psutil==6.0.0 # via reboot pycparser==3.0 # via cffi -pydantic==2.12.5 +pydantic==2.13.3 # via fastapi + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot -pydantic-core==2.41.5 +pydantic-ai-slim==1.87.0 + # via reboot +pydantic-core==2.46.3 # via pydantic -pydantic-settings==2.12.0 +pydantic-graph==1.87.0 + # via pydantic-ai-slim +pydantic-settings==2.14.0 # via mcp pyjwt==2.10.1 # via mcp @@ -186,23 +204,23 @@ pyprctl==0.1.3 # via reboot python-dateutil==2.9.0.post0 # via kubernetes-asyncio -python-dotenv==1.2.1 +python-dotenv==1.2.2 # via pydantic-settings -python-multipart==0.0.21 +python-multipart==0.0.27 # via mcp python-ulid==3.1.0 # via reboot pyyaml==6.0.2 # via kubernetes-asyncio # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications rpds-py==0.30.0 # via jsonschema # via referencing -setuptools==80.10.1 +setuptools==82.0.1 # via grpcio-tools six==1.17.0 # via kubernetes-asyncio @@ -215,9 +233,9 @@ starlette==0.46.2 # via reboot tabulate==0.9.0 # via reboot -tomli==2.4.0 +tomli==2.4.1 # via mypy -types-protobuf==6.32.1.20251210 +types-protobuf==7.34.1.20260408 # via mypy-protobuf typing-extensions==4.15.0 # via aiosignal @@ -230,12 +248,15 @@ typing-extensions==4.15.0 # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via typing-inspection # via uvicorn typing-inspection==0.4.2 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot @@ -256,7 +277,7 @@ wrapt==1.17.3 # via deprecated # via opentelemetry-instrumentation # via opentelemetry-instrumentation-grpc -yarl==1.22.0 +yarl==1.23.0 # via aiohttp -zipp==3.23.0 +zipp==3.23.1 # via importlib-metadata diff --git a/reboot/examples/ai-chat-counter/requirements.lock b/reboot/examples/ai-chat-counter/requirements.lock index 1d38fdeb..b9ce664b 100644 --- a/reboot/examples/ai-chat-counter/requirements.lock +++ b/reboot/examples/ai-chat-counter/requirements.lock @@ -18,49 +18,54 @@ aiosignal==1.4.0 # via aiohttp annotated-types==0.7.0 # via pydantic -anyio==4.12.1 +anyio==4.9.0 # via httpx # via mcp # via reboot # via sse-starlette # via starlette -async-timeout==5.0.1 +async-timeout==4.0.3 # via aiohttp -attrs==25.4.0 +attrs==24.2.0 # via aiohttp # via jsonschema # via referencing bitarray==3.8.0 # via reboot -certifi==2026.1.4 +certifi==2024.8.30 # via httpcore # via httpx # via kubernetes-asyncio cffi==1.17.1 # via cryptography # via reboot -click==8.3.1 +click==8.1.8 # via uvicorn colorama==0.4.6 # via reboot cryptography==44.0.0 # via pyjwt # via reboot -deprecated==1.3.1 +deprecated==1.2.15 # via opentelemetry-api # via opentelemetry-exporter-otlp-proto-grpc # via opentelemetry-semantic-conventions -exceptiongroup==1.3.1 +exceptiongroup==1.2.2 # via anyio + # via pydantic-ai-slim fastapi==0.115.12 # via reboot -frozenlist==1.8.0 +frozenlist==1.4.1 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -79,22 +84,25 @@ grpcio-status==1.64.3 # via reboot grpcio-tools==1.64.3 # via reboot -h11==0.16.0 +h11==0.14.0 # via httpcore # via uvicorn h2==4.3.0 # via reboot hpack==4.1.0 # via h2 -httpcore==1.0.9 +httpcore==1.0.8 # via httpx httpx==0.28.1 + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 # via h2 -idna==3.11 +idna==3.10 # via anyio # via httpx # via yarl @@ -111,11 +119,13 @@ jsonschema-specifications==2025.9.1 # via jsonschema kubernetes-asyncio==31.1.0 # via reboot -markupsafe==3.0.3 +logfire-api==4.32.1 + # via pydantic-graph +markupsafe==2.1.5 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot -multidict==6.7.1 +multidict==6.1.0 # via aiohttp # via yarl mypy-protobuf==3.6.0 @@ -126,6 +136,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -145,7 +156,7 @@ opentelemetry-semantic-conventions==0.49b1 # via opentelemetry-instrumentation # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk -packaging==26.0 +packaging==23.1 # via opentelemetry-instrumentation # via reboot pathspec==0.12.1 @@ -164,15 +175,22 @@ protobuf==5.28.3 # via reboot psutil==6.0.0 # via reboot -pycparser==3.0 +pycparser==2.22 # via cffi pydantic==2.12.5 # via fastapi + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot +pydantic-ai-slim==1.87.0 + # via reboot pydantic-core==2.41.5 # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim pydantic-settings==2.13.1 # via mcp pyjwt==2.10.1 @@ -191,18 +209,20 @@ python-ulid==3.1.0 pyyaml==6.0.2 # via kubernetes-asyncio # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications rpds-py==0.30.0 # via jsonschema # via referencing -setuptools==82.0.1 +setuptools==75.1.0 # via grpcio-tools -six==1.17.0 +six==1.16.0 # via kubernetes-asyncio # via python-dateutil +sniffio==1.3.1 + # via anyio sse-starlette==3.0.3 # via mcp starlette==0.46.2 @@ -211,24 +231,26 @@ starlette==0.46.2 # via reboot tabulate==0.9.0 # via reboot -types-protobuf==6.32.1.20260221 +types-protobuf==5.28.0.20240924 # via mypy-protobuf typing-extensions==4.15.0 # via aiosignal # via anyio - # via exceptiongroup # via fastapi # via mcp # via multidict # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via typing-inspection # via uvicorn typing-inspection==0.4.2 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot @@ -245,11 +267,11 @@ watchdog==6.0.0 # via reboot websockets==15.0.1 # via reboot -wrapt==1.17.3 +wrapt==1.16.0 # via deprecated # via opentelemetry-instrumentation # via opentelemetry-instrumentation-grpc -yarl==1.23.0 +yarl==1.22.0 # via aiohttp -zipp==3.23.0 +zipp==3.21.0 # via importlib-metadata diff --git a/reboot/examples/ai-chat-counter/web/package-lock.json b/reboot/examples/ai-chat-counter/web/package-lock.json index 6790b86a..442df05e 100644 --- a/reboot/examples/ai-chat-counter/web/package-lock.json +++ b/reboot/examples/ai-chat-counter/web/package-lock.json @@ -8,10 +8,10 @@ "name": "ai-chat-counter-web", "version": "0.1.0", "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-react": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", "zod": "^3.25.0" @@ -28,8 +28,6 @@ }, "node_modules/@babel/code-frame": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -43,8 +41,6 @@ }, "node_modules/@babel/compat-data": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", - "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", "dev": true, "license": "MIT", "engines": { @@ -53,8 +49,6 @@ }, "node_modules/@babel/core": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", - "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", "dependencies": { @@ -84,8 +78,6 @@ }, "node_modules/@babel/generator": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", - "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", "dev": true, "license": "MIT", "dependencies": { @@ -101,8 +93,6 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { @@ -118,8 +108,6 @@ }, "node_modules/@babel/helper-globals": { "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, "license": "MIT", "engines": { @@ -128,8 +116,6 @@ }, "node_modules/@babel/helper-module-imports": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { @@ -142,8 +128,6 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { @@ -160,8 +144,6 @@ }, "node_modules/@babel/helper-plugin-utils": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { @@ -170,8 +152,6 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -180,8 +160,6 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -190,8 +168,6 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -200,8 +176,6 @@ }, "node_modules/@babel/helpers": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "dependencies": { @@ -214,8 +188,6 @@ }, "node_modules/@babel/parser": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", "dev": true, "license": "MIT", "dependencies": { @@ -230,8 +202,6 @@ }, "node_modules/@babel/plugin-transform-react-jsx-self": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", "dev": true, "license": "MIT", "dependencies": { @@ -246,8 +216,6 @@ }, "node_modules/@babel/plugin-transform-react-jsx-source": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", "dev": true, "license": "MIT", "dependencies": { @@ -262,8 +230,6 @@ }, "node_modules/@babel/template": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { @@ -277,8 +243,6 @@ }, "node_modules/@babel/traverse": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", "dev": true, "license": "MIT", "dependencies": { @@ -296,8 +260,6 @@ }, "node_modules/@babel/types": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", "dev": true, "license": "MIT", "dependencies": { @@ -310,440 +272,11 @@ }, "node_modules/@bufbuild/protobuf": { "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.1.tgz", - "integrity": "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==", "license": "(Apache-2.0 AND BSD-3-Clause)", "peer": true }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { + "node_modules/@esbuild/linux-x64": { "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -751,7 +284,7 @@ "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ], "engines": { "node": ">=18" @@ -759,8 +292,6 @@ }, "node_modules/@hono/node-server": { "version": "1.19.9", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", - "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -771,8 +302,6 @@ }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { @@ -782,8 +311,6 @@ }, "node_modules/@jridgewell/remapping": { "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", "dependencies": { @@ -793,8 +320,6 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { @@ -803,15 +328,11 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -820,15 +341,16 @@ } }, "node_modules/@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", "license": "MIT", "workspaces": [ "examples/*" ], + "engines": { + "node": ">=20" + }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" @@ -843,9 +365,7 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -883,9 +403,9 @@ } }, "node_modules/@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0", @@ -898,8 +418,6 @@ }, "node_modules/@reboot-dev/reboot-api/node_modules/typescript": { "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -910,15 +428,15 @@ } }, "node_modules/@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", "license": "Apache-2.0", "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -934,8 +452,6 @@ }, "node_modules/@reboot-dev/reboot-react/node_modules/typescript": { "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -946,12 +462,12 @@ } }, "node_modules/@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -978,253 +494,11 @@ }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", "dev": true, "license": "MIT" }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz", - "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz", - "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz", - "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz", - "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz", - "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz", - "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz", - "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz", - "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz", - "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz", - "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz", - "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz", - "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz", - "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz", - "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz", - "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz", - "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz", - "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", - "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", "cpu": [ "x64" ], @@ -1237,8 +511,6 @@ }, "node_modules/@rollup/rollup-linux-x64-musl": { "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz", - "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==", "cpu": [ "x64" ], @@ -1249,101 +521,13 @@ "linux" ] }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz", - "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz", - "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz", - "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz", - "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz", - "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", - "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@scarf/scarf": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", - "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", "hasInstallScript": true, "license": "Apache-2.0" }, "node_modules/@types/babel__core": { "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "license": "MIT", "dependencies": { @@ -1356,8 +540,6 @@ }, "node_modules/@types/babel__generator": { "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, "license": "MIT", "dependencies": { @@ -1366,8 +548,6 @@ }, "node_modules/@types/babel__template": { "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "license": "MIT", "dependencies": { @@ -1377,8 +557,6 @@ }, "node_modules/@types/babel__traverse": { "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1387,22 +565,16 @@ }, "node_modules/@types/estree": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, "node_modules/@types/prop-types": { "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "dev": true, "license": "MIT", "dependencies": { @@ -1412,8 +584,6 @@ }, "node_modules/@types/react-dom": { "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1422,14 +592,10 @@ }, "node_modules/@types/uuid": { "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", - "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", "license": "MIT" }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", "dev": true, "license": "MIT", "dependencies": { @@ -1449,8 +615,6 @@ }, "node_modules/accepts": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -1462,8 +626,6 @@ }, "node_modules/ajv": { "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1478,8 +640,6 @@ }, "node_modules/ajv-formats": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -1495,8 +655,6 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -1505,8 +663,6 @@ }, "node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -1521,8 +677,6 @@ }, "node_modules/baseline-browser-mapping": { "version": "2.9.18", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz", - "integrity": "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1531,8 +685,6 @@ }, "node_modules/body-parser": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", @@ -1555,8 +707,6 @@ }, "node_modules/braces": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { @@ -1568,8 +718,6 @@ }, "node_modules/browserslist": { "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -1602,8 +750,6 @@ }, "node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -1611,8 +757,6 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -1624,8 +768,6 @@ }, "node_modules/call-bound": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -1640,8 +782,6 @@ }, "node_modules/caniuse-lite": { "version": "1.0.30001766", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", - "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", "dev": true, "funding": [ { @@ -1661,8 +801,6 @@ }, "node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { @@ -1678,8 +816,6 @@ }, "node_modules/chalk/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -1691,8 +827,6 @@ }, "node_modules/cliui": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "license": "ISC", "dependencies": { @@ -1706,8 +840,6 @@ }, "node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1719,15 +851,11 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "license": "MIT" }, "node_modules/concurrently": { "version": "9.2.1", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", - "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", "dev": true, "license": "MIT", "dependencies": { @@ -1751,8 +879,6 @@ }, "node_modules/content-disposition": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", "engines": { "node": ">=18" @@ -1764,8 +890,6 @@ }, "node_modules/content-type": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1773,15 +897,11 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, "node_modules/cookie": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -1789,8 +909,6 @@ }, "node_modules/cookie-signature": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", "engines": { "node": ">=6.6.0" @@ -1798,8 +916,6 @@ }, "node_modules/cors": { "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -1815,8 +931,6 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -1829,15 +943,11 @@ }, "node_modules/csstype": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "dev": true, "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1853,8 +963,6 @@ }, "node_modules/depd": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -1862,8 +970,6 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -1876,28 +982,20 @@ }, "node_modules/ee-first": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, "node_modules/electron-to-chromium": { "version": "1.5.279", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.279.tgz", - "integrity": "sha512-0bblUU5UNdOt5G7XqGiJtpZMONma6WAfq9vsFmtn9x1+joAObr6x1chfqyxFSDCAFwFhCQDrqeAr6MYdpwJ9Hg==", "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -1905,8 +1003,6 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -1914,8 +1010,6 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -1923,8 +1017,6 @@ }, "node_modules/es-object-atoms": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -1935,8 +1027,6 @@ }, "node_modules/esbuild": { "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1977,8 +1067,6 @@ }, "node_modules/escalade": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", "engines": { @@ -1987,14 +1075,10 @@ }, "node_modules/escape-html": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, "node_modules/etag": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -2002,8 +1086,6 @@ }, "node_modules/eventsource": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", "license": "MIT", "dependencies": { "eventsource-parser": "^3.0.1" @@ -2014,8 +1096,6 @@ }, "node_modules/eventsource-parser": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -2023,8 +1103,6 @@ }, "node_modules/express": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", @@ -2066,8 +1144,6 @@ }, "node_modules/express-rate-limit": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.0.tgz", - "integrity": "sha512-KJzBawY6fB9FiZGdE/0aftepZ91YlaGIrV8vgblRM3J8X+dHx/aiowJWwkx6LIGyuqGiANsjSwwrbb8mifOJ4Q==", "license": "MIT", "dependencies": { "ip-address": "10.1.0" @@ -2084,14 +1160,10 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, "node_modules/fast-uri": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "funding": [ { "type": "github", @@ -2106,8 +1178,6 @@ }, "node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -2124,8 +1194,6 @@ }, "node_modules/fill-range": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { @@ -2137,8 +1205,6 @@ }, "node_modules/finalhandler": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -2158,8 +1224,6 @@ }, "node_modules/forwarded": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -2167,32 +1231,13 @@ }, "node_modules/fresh": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2200,8 +1245,6 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "license": "MIT", "engines": { @@ -2210,8 +1253,6 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, "license": "ISC", "engines": { @@ -2220,8 +1261,6 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -2244,8 +1283,6 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -2257,8 +1294,6 @@ }, "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -2269,8 +1304,6 @@ }, "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -2279,8 +1312,6 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -2291,8 +1322,6 @@ }, "node_modules/hasown": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -2303,8 +1332,6 @@ }, "node_modules/hono": { "version": "4.11.6", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.6.tgz", - "integrity": "sha512-ofIiiHyl34SV6AuhE3YT2mhO5HRWokce+eUYE82TsP6z0/H3JeJcjVWEMSIAiw2QkjDOEpES/lYsg8eEbsLtdw==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -2312,8 +1339,6 @@ }, "node_modules/http-errors": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { "depd": "~2.0.0", @@ -2332,8 +1357,6 @@ }, "node_modules/iconv-lite": { "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -2348,14 +1371,10 @@ }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, "node_modules/ip-address": { "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "license": "MIT", "engines": { "node": ">= 12" @@ -2363,8 +1382,6 @@ }, "node_modules/ipaddr.js": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -2372,8 +1389,6 @@ }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", "engines": { @@ -2382,8 +1397,6 @@ }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { @@ -2392,20 +1405,14 @@ }, "node_modules/is-promise": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, "node_modules/jose": { "version": "6.1.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", - "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -2419,14 +1426,10 @@ }, "node_modules/js-tokens": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, "node_modules/jsesc": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", "bin": { @@ -2438,20 +1441,14 @@ }, "node_modules/json-schema-traverse": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, "node_modules/json-schema-typed": { "version": "8.0.2", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", "license": "BSD-2-Clause" }, "node_modules/json5": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", "bin": { @@ -2463,8 +1460,6 @@ }, "node_modules/loose-envify": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -2475,8 +1470,6 @@ }, "node_modules/lru-cache": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "license": "ISC", "dependencies": { @@ -2491,8 +1484,6 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -2500,8 +1491,6 @@ }, "node_modules/media-typer": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -2509,8 +1498,6 @@ }, "node_modules/merge-descriptors": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { "node": ">=18" @@ -2521,8 +1508,6 @@ }, "node_modules/micromatch": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { @@ -2535,8 +1520,6 @@ }, "node_modules/micromatch/node_modules/picomatch": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { @@ -2548,8 +1531,6 @@ }, "node_modules/mime-db": { "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -2557,8 +1538,6 @@ }, "node_modules/mime-types": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -2573,14 +1552,10 @@ }, "node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -2598,8 +1573,6 @@ }, "node_modules/negotiator": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -2607,15 +1580,11 @@ }, "node_modules/node-releases": { "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, "node_modules/object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2623,8 +1592,6 @@ }, "node_modules/object-inspect": { "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -2635,8 +1602,6 @@ }, "node_modules/on-finished": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -2647,8 +1612,6 @@ }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { "wrappy": "1" @@ -2656,8 +1619,6 @@ }, "node_modules/parseurl": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -2665,8 +1626,6 @@ }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", "engines": { "node": ">=8" @@ -2674,8 +1633,6 @@ }, "node_modules/path-to-regexp": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "license": "MIT", "funding": { "type": "opencollective", @@ -2684,15 +1641,11 @@ }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -2704,8 +1657,6 @@ }, "node_modules/pkce-challenge": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", "license": "MIT", "engines": { "node": ">=16.20.0" @@ -2713,8 +1664,6 @@ }, "node_modules/postcss": { "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -2742,8 +1691,6 @@ }, "node_modules/proxy-addr": { "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -2755,8 +1702,6 @@ }, "node_modules/qs": { "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -2770,8 +1715,6 @@ }, "node_modules/range-parser": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -2779,8 +1722,6 @@ }, "node_modules/raw-body": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -2794,8 +1735,6 @@ }, "node_modules/react": { "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -2806,8 +1745,6 @@ }, "node_modules/react-dom": { "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -2819,8 +1756,6 @@ }, "node_modules/react-refresh": { "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", "dev": true, "license": "MIT", "engines": { @@ -2829,8 +1764,6 @@ }, "node_modules/require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { @@ -2839,8 +1772,6 @@ }, "node_modules/require-from-string": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2848,8 +1779,6 @@ }, "node_modules/rollup": { "version": "4.56.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", - "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", "dev": true, "license": "MIT", "dependencies": { @@ -2893,8 +1822,6 @@ }, "node_modules/router": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -2909,8 +1836,6 @@ }, "node_modules/rxjs": { "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2919,14 +1844,10 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, "node_modules/scheduler": { "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -2934,8 +1855,6 @@ }, "node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -2944,8 +1863,6 @@ }, "node_modules/send": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { "debug": "^4.4.3", @@ -2970,8 +1887,6 @@ }, "node_modules/serve-static": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -2989,14 +1904,10 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -3007,8 +1918,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", "engines": { "node": ">=8" @@ -3016,8 +1925,6 @@ }, "node_modules/shell-quote": { "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", "dev": true, "license": "MIT", "engines": { @@ -3029,8 +1936,6 @@ }, "node_modules/side-channel": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3048,8 +1953,6 @@ }, "node_modules/side-channel-list": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3064,8 +1967,6 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -3082,8 +1983,6 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -3101,8 +2000,6 @@ }, "node_modules/source-map-js": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -3111,8 +2008,6 @@ }, "node_modules/statuses": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -3120,8 +2015,6 @@ }, "node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "license": "MIT", "dependencies": { @@ -3135,8 +2028,6 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -3148,8 +2039,6 @@ }, "node_modules/supports-color": { "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3164,8 +2053,6 @@ }, "node_modules/tinyglobby": { "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3181,8 +2068,6 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3194,8 +2079,6 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", "engines": { "node": ">=0.6" @@ -3203,8 +2086,6 @@ }, "node_modules/tree-kill": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, "license": "MIT", "bin": { @@ -3213,14 +2094,10 @@ }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, "node_modules/type-is": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { "content-type": "^1.0.5", @@ -3233,8 +2110,6 @@ }, "node_modules/typescript": { "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3247,8 +2122,6 @@ }, "node_modules/unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -3256,8 +2129,6 @@ }, "node_modules/update-browserslist-db": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -3300,8 +2171,6 @@ }, "node_modules/vary": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -3309,8 +2178,6 @@ }, "node_modules/vite": { "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", "dependencies": { @@ -3384,8 +2251,6 @@ }, "node_modules/vite-plugin-singlefile": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-2.3.0.tgz", - "integrity": "sha512-DAcHzYypM0CasNLSz/WG0VdKOCxGHErfrjOoyIPiNxTPTGmO6rRD/te93n1YL/s+miXq66ipF1brMBikf99c6A==", "dev": true, "license": "MIT", "dependencies": { @@ -3401,8 +2266,6 @@ }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -3416,8 +2279,6 @@ }, "node_modules/wrap-ansi": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", "dependencies": { @@ -3434,14 +2295,10 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, "node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, "license": "ISC", "engines": { @@ -3450,15 +2307,11 @@ }, "node_modules/yallist": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" }, "node_modules/yargs": { "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "license": "MIT", "dependencies": { @@ -3476,8 +2329,6 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, "license": "ISC", "engines": { @@ -3486,8 +2337,6 @@ }, "node_modules/zod": { "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -3495,8 +2344,6 @@ }, "node_modules/zod-to-json-schema": { "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", "license": "ISC", "peerDependencies": { "zod": "^3.25 || ^4" diff --git a/reboot/examples/ai-chat-counter/web/package.json b/reboot/examples/ai-chat-counter/web/package.json index 1de9ca1e..6a063be2 100644 --- a/reboot/examples/ai-chat-counter/web/package.json +++ b/reboot/examples/ai-chat-counter/web/package.json @@ -11,10 +11,10 @@ "build:watch": "npm run build:watch:clicker" }, "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-react": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", "zod": "^3.25.0" diff --git a/reboot/examples/all_tests.sh b/reboot/examples/all_tests.sh index 82d52376..9f45ffde 100755 --- a/reboot/examples/all_tests.sh +++ b/reboot/examples/all_tests.sh @@ -10,7 +10,7 @@ set -x # Echo executed commands to help debug failures. # Check that this script has been invoked with the right working directory, by # checking that the expected subdirectories exist. -ls -l bank-pydantic/ bank-zod/ docubot/ bank-nodejs/ boutique/ chat-room/ chat-room-nodejs/ monorepo/ 2> /dev/null > /dev/null || { +ls -l agent-wiki/ bank-pydantic/ bank-zod/ chick-potle/ docubot/ bank-nodejs/ boutique/ chat-room/ chat-room-nodejs/ monorepo/ 2> /dev/null > /dev/null || { echo "ERROR: this script must be invoked from the 'reboot/examples' directory." echo "Current working directory is '$(pwd)'." ls @@ -21,8 +21,10 @@ ls -l bank-pydantic/ bank-zod/ docubot/ bank-nodejs/ boutique/ chat-room/ chat-r export SANDBOX_ROOT="" # Run all of the tests. +sh -c 'cd agent-wiki && ./.tests/test.sh' sh -c 'cd bank-pydantic && ./.tests/test.sh' sh -c 'cd bank-zod && ./.tests/test.sh' +sh -c 'cd chick-potle && ./.tests/test.sh' sh -c 'cd docubot && ./.tests/test.sh' sh -c 'cd bank-nodejs && ./.tests/test.sh' sh -c 'cd boutique && ./.tests/test.sh' diff --git a/reboot/examples/bank-nodejs/.rbtrc b/reboot/examples/bank-nodejs/.rbtrc index 77e81bcd..5d3d59e4 100644 --- a/reboot/examples/bank-nodejs/.rbtrc +++ b/reboot/examples/bank-nodejs/.rbtrc @@ -10,10 +10,10 @@ generate --nodejs-extensions dev run --watch=backend/src/**/*.ts -dev run --name=bank-nodejs +dev run --application-name=bank-nodejs dev run --nodejs dev run --application=backend/src/main.ts -dev expunge --name=bank-nodejs +dev expunge --application-name=bank-nodejs diff --git a/reboot/examples/bank-nodejs/package-lock.json b/reboot/examples/bank-nodejs/package-lock.json index 7c36600e..5577850e 100644 --- a/reboot/examples/bank-nodejs/package-lock.json +++ b/reboot/examples/bank-nodejs/package-lock.json @@ -9,8 +9,8 @@ "version": "0.1.0", "dependencies": { "@bufbuild/protobuf": "1.10.1", - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", "@types/node": "20.11.5", "typescript": "5.4.5" }, @@ -510,15 +510,15 @@ } }, "node_modules/@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -547,9 +547,9 @@ } }, "node_modules/@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0", @@ -3119,13 +3119,13 @@ "optional": true }, "@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "requires": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -3142,9 +3142,9 @@ } }, "@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "requires": { "@scarf/scarf": "1.4.0", "typescript": "5.4.5", diff --git a/reboot/examples/bank-nodejs/package.json b/reboot/examples/bank-nodejs/package.json index fae329f0..7bed5b06 100644 --- a/reboot/examples/bank-nodejs/package.json +++ b/reboot/examples/bank-nodejs/package.json @@ -5,8 +5,8 @@ "type": "module", "dependencies": { "@bufbuild/protobuf": "1.10.1", - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", "typescript": "5.4.5", "@types/node": "20.11.5" }, diff --git a/reboot/examples/bank-pydantic/.rbtrc b/reboot/examples/bank-pydantic/.rbtrc index cdc1faed..02866f4c 100644 --- a/reboot/examples/bank-pydantic/.rbtrc +++ b/reboot/examples/bank-pydantic/.rbtrc @@ -15,10 +15,10 @@ dev run --watch=backend/**/*.py dev run --python # Save state between chaos restarts. -dev run --name=bank +dev run --application-name=bank # Run the application! dev run --application=backend/src/main.py # When expunging, expunge that state we've saved. -dev expunge --name=bank +dev expunge --application-name=bank diff --git a/reboot/examples/bank-pydantic/api/bank/v1/pydantic/account.py b/reboot/examples/bank-pydantic/api/bank/v1/pydantic/account.py index ff19a246..6ee0ef89 100644 --- a/reboot/examples/bank-pydantic/api/bank/v1/pydantic/account.py +++ b/reboot/examples/bank-pydantic/api/bank/v1/pydantic/account.py @@ -25,25 +25,30 @@ class OverdraftError(Model): balance=Reader( request=None, response=BalanceResponse, + mcp=None, ), deposit=Writer( request=DepositRequest, response=None, + mcp=None, ), withdraw=Writer( request=WithdrawRequest, response=None, errors=[OverdraftError], + mcp=None, ), # Must use this method to create an instance of Account. open=Writer( request=None, response=None, factory=True, + mcp=None, ), interest=Writer( request=None, response=None, + mcp=None, ), ) diff --git a/reboot/examples/bank-pydantic/api/bank/v1/pydantic/bank.py b/reboot/examples/bank-pydantic/api/bank/v1/pydantic/bank.py index fec348d5..0ef00595 100644 --- a/reboot/examples/bank-pydantic/api/bank/v1/pydantic/bank.py +++ b/reboot/examples/bank-pydantic/api/bank/v1/pydantic/bank.py @@ -43,26 +43,32 @@ class AccountBalancesResponse(Model): request=None, response=None, factory=True, + mcp=None, ), sign_up=Transaction( request=SignUpRequest, response=None, + mcp=None, ), all_customer_ids=Reader( request=None, response=AllCustomerIdsResponse, + mcp=None, ), transfer=Transaction( request=TransferRequest, response=None, + mcp=None, ), open_customer_account=Transaction( request=OpenCustomerAccountRequest, response=None, + mcp=None, ), account_balances=Reader( request=None, response=AccountBalancesResponse, + mcp=None, ), ) diff --git a/reboot/examples/bank-pydantic/pyproject.toml b/reboot/examples/bank-pydantic/pyproject.toml index 142168e1..1c50821b 100644 --- a/reboot/examples/bank-pydantic/pyproject.toml +++ b/reboot/examples/bank-pydantic/pyproject.toml @@ -1,7 +1,7 @@ [project] requires-python = ">= 3.10" dependencies = [ - "reboot==0.46.0", + "reboot==1.0.3", ] [tool.rye] @@ -9,7 +9,7 @@ dev-dependencies = [ "mypy==1.18.1", "pytest>=7.4.2", "types-protobuf>=4.24.0.20240129", - "reboot==0.46.0", + "reboot==1.0.3", ] # This project only uses `rye` to provide `python` and its dependencies, so diff --git a/reboot/examples/bank-pydantic/requirements-dev.lock b/reboot/examples/bank-pydantic/requirements-dev.lock index 39e69215..bcdb6caf 100644 --- a/reboot/examples/bank-pydantic/requirements-dev.lock +++ b/reboot/examples/bank-pydantic/requirements-dev.lock @@ -52,16 +52,21 @@ deprecated==1.2.18 # via opentelemetry-semantic-conventions exceptiongroup==1.3.0 # via anyio + # via pydantic-ai-slim # via pytest fastapi==0.115.12 # via reboot frozenlist==1.8.0 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -90,7 +95,10 @@ hpack==4.1.0 httpcore==1.0.9 # via httpx httpx==0.28.1 + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 @@ -114,9 +122,11 @@ jsonschema-specifications==2025.9.1 # via jsonschema kubernetes-asyncio==31.1.0 # via reboot +logfire-api==4.32.1 + # via pydantic-graph markupsafe==3.0.3 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot multidict==6.7.0 # via aiohttp @@ -132,6 +142,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -178,11 +189,18 @@ pycparser==2.23 # via cffi pydantic==2.12.0 # via fastapi + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot +pydantic-ai-slim==1.87.0 + # via reboot pydantic-core==2.41.1 # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim pydantic-settings==2.13.1 # via mcp pygments==2.19.2 @@ -204,7 +222,7 @@ python-ulid==3.1.0 pyyaml==6.0.2 # via kubernetes-asyncio # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications @@ -242,12 +260,15 @@ typing-extensions==4.15.0 # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via typing-inspection # via uvicorn typing-inspection==0.4.2 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot diff --git a/reboot/examples/bank-pydantic/requirements.lock b/reboot/examples/bank-pydantic/requirements.lock index 997670d5..9ea6338b 100644 --- a/reboot/examples/bank-pydantic/requirements.lock +++ b/reboot/examples/bank-pydantic/requirements.lock @@ -52,15 +52,20 @@ deprecated==1.2.18 # via opentelemetry-semantic-conventions exceptiongroup==1.3.0 # via anyio + # via pydantic-ai-slim fastapi==0.115.12 # via reboot frozenlist==1.8.0 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -89,7 +94,10 @@ hpack==4.1.0 httpcore==1.0.9 # via httpx httpx==0.28.1 + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 @@ -111,9 +119,11 @@ jsonschema-specifications==2025.9.1 # via jsonschema kubernetes-asyncio==31.1.0 # via reboot +logfire-api==4.32.1 + # via pydantic-graph markupsafe==3.0.3 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot multidict==6.7.0 # via aiohttp @@ -126,6 +136,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -168,11 +179,18 @@ pycparser==2.23 # via cffi pydantic==2.12.0 # via fastapi + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot +pydantic-ai-slim==1.87.0 + # via reboot pydantic-core==2.41.1 # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim pydantic-settings==2.13.1 # via mcp pyjwt==2.10.1 @@ -191,7 +209,7 @@ python-ulid==3.1.0 pyyaml==6.0.2 # via kubernetes-asyncio # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications @@ -225,12 +243,15 @@ typing-extensions==4.15.0 # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via typing-inspection # via uvicorn typing-inspection==0.4.2 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot diff --git a/reboot/examples/bank-pydantic/web/package-lock.json b/reboot/examples/bank-pydantic/web/package-lock.json index 2cc71cab..1a6876d3 100644 --- a/reboot/examples/bank-pydantic/web/package-lock.json +++ b/reboot/examples/bank-pydantic/web/package-lock.json @@ -9,9 +9,9 @@ "version": "0.1.0", "dependencies": { "@eslint/js": "^9.34.0", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-react": "0.46.0", - "@reboot-dev/reboot-std": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", + "@reboot-dev/reboot-std": "1.0.3", "@tailwindcss/vite": "^4.1.11", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -972,19 +972,10 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@gar/promise-retry": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", - "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", - "license": "MIT", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -1089,15 +1080,18 @@ } }, "node_modules/@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "license": "MIT", "workspaces": [ "examples/*" ], + "engines": { + "node": ">=20" + }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" @@ -1112,9 +1106,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -1152,9 +1146,9 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1205,74 +1199,16 @@ "node": ">= 8" } }, - "node_modules/@npmcli/agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", - "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", - "license": "ISC", - "dependencies": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^11.2.1", - "socks-proxy-agent": "^8.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@npmcli/fs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", - "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", - "license": "ISC", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/redact": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", - "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -1301,9 +1237,9 @@ } }, "node_modules/@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0", @@ -1327,15 +1263,15 @@ } }, "node_modules/@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", "license": "Apache-2.0", "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -1362,32 +1298,32 @@ } }, "node_modules/@reboot-dev/reboot-std": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std/-/reboot-std-0.46.0.tgz", - "integrity": "sha512-Sd915ak5YKuLcEteA/ULzJsehiDp3/TSrPHXIw1Osza9Yqakewntv47QsLgTnqCBxhpvQ49WbTqK6iEJQ1Sgqw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std/-/reboot-std-1.0.3.tgz", + "integrity": "sha512-+N6yWU6uVXnJikK8ECh3NuiGo47zfP8ThE34SJCyXzSSJQS7suf9sS2yLCNaWbSKt7vTOrx9clAF9JjEnhoF7Q==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-std-api": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-std-api": "1.0.3", "@scarf/scarf": "1.4.0" } }, "node_modules/@reboot-dev/reboot-std-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-api/-/reboot-std-api-0.46.0.tgz", - "integrity": "sha512-SPt7RsLdJoGxxrf/896yC7/lqBNqVmAPL2BFpZbyCV+Bb205VAodm7ERylhvaFEU7PgucKOrjR7WXXSTeXDo6w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-api/-/reboot-std-api-1.0.3.tgz", + "integrity": "sha512-k6aRTEuL40ki4FN51A7UdkvGK2bAOJ5npStWkL3tpLhIwtmcmdtbNZZeYEdVgPCLEgReIep9bd61U6ObnNzXtA==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0" } }, "node_modules/@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -3078,9 +3014,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -3294,36 +3230,6 @@ "node": ">=8" } }, - "node_modules/cacache": { - "version": "20.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", - "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", - "license": "ISC", - "dependencies": { - "@npmcli/fs": "^5.0.0", - "fs-minipass": "^3.0.0", - "glob": "^13.0.0", - "lru-cache": "^11.1.0", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^13.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -3463,9 +3369,9 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "license": "MIT", "engines": { "node": ">=18" @@ -4044,9 +3950,9 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -4110,9 +4016,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "license": "MIT", "dependencies": { "ip-address": "10.1.0" @@ -4305,18 +4211,6 @@ "node": ">= 0.8" } }, - "node_modules/fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4400,23 +4294,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4428,51 +4305,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob/node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -4572,9 +4404,9 @@ } }, "node_modules/hono": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", - "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==", + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -4591,12 +4423,6 @@ "node": ">=18" } }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "license": "BSD-2-Clause" - }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -4988,9 +4814,9 @@ } }, "node_modules/jose": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", - "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -5409,29 +5235,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/make-fetch-happen": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.5.tgz", - "integrity": "sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg==", - "license": "ISC", - "dependencies": { - "@gar/promise-retry": "^1.0.0", - "@npmcli/agent": "^4.0.0", - "@npmcli/redact": "^4.0.0", - "cacache": "^20.0.1", - "http-cache-semantics": "^4.1.1", - "minipass": "^7.0.2", - "minipass-fetch": "^5.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^1.0.0", - "proc-log": "^6.0.0", - "ssri": "^13.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -5525,124 +5328,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/minipass-collect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minipass-fetch": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", - "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^2.0.0", - "minizlib": "^3.0.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - }, - "optionalDependencies": { - "iconv-lite": "^0.7.2" - } - }, - "node_modules/minipass-fetch/node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", - "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-flush/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-pipeline/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/minipass-sized": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", - "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", - "license": "ISC", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/minizlib": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", @@ -5697,20 +5382,20 @@ "license": "MIT" }, "node_modules/node-gyp": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz", - "integrity": "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz", + "integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==", "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^15.0.0", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", + "undici": "^6.25.0", "which": "^6.0.0" }, "bin": { @@ -5908,18 +5593,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5967,35 +5640,10 @@ "node": ">=8" } }, - "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", "funding": { "type": "opencollective", @@ -6138,9 +5786,9 @@ } }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -6604,44 +6252,6 @@ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==" }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "license": "MIT", - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6650,18 +6260,6 @@ "node": ">=0.10.0" } }, - "node_modules/ssri": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", - "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -7014,6 +6612,15 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/undici": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", + "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -7500,12 +7107,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", "peerDependencies": { - "zod": "^3.25 || ^4" + "zod": "^3.25.28 || ^4" } } }, @@ -8004,15 +7611,10 @@ "levn": "^0.4.1" } }, - "@gar/promise-retry": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", - "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==" - }, "@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "requires": {} }, "@humanfs/core": { @@ -8085,15 +7687,15 @@ } }, "@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "requires": {} }, "@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "requires": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -8115,9 +7717,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -8155,53 +7757,14 @@ "fastq": "^1.6.0" } }, - "@npmcli/agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", - "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", - "requires": { - "agent-base": "^7.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.1", - "lru-cache": "^11.2.1", - "socks-proxy-agent": "^8.0.3" - }, - "dependencies": { - "lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==" - } - } - }, - "@npmcli/fs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", - "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", - "requires": { - "semver": "^7.3.5" - }, - "dependencies": { - "semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==" - } - } - }, - "@npmcli/redact": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", - "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==" - }, "@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "requires": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -8402,9 +7965,9 @@ } }, "@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "requires": { "@scarf/scarf": "1.4.0", "typescript": "5.4.5", @@ -8419,14 +7982,14 @@ } }, "@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", - "requires": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", + "requires": { + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -8443,29 +8006,29 @@ } }, "@reboot-dev/reboot-std": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std/-/reboot-std-0.46.0.tgz", - "integrity": "sha512-Sd915ak5YKuLcEteA/ULzJsehiDp3/TSrPHXIw1Osza9Yqakewntv47QsLgTnqCBxhpvQ49WbTqK6iEJQ1Sgqw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std/-/reboot-std-1.0.3.tgz", + "integrity": "sha512-+N6yWU6uVXnJikK8ECh3NuiGo47zfP8ThE34SJCyXzSSJQS7suf9sS2yLCNaWbSKt7vTOrx9clAF9JjEnhoF7Q==", "requires": { - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-std-api": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-std-api": "1.0.3", "@scarf/scarf": "1.4.0" } }, "@reboot-dev/reboot-std-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-api/-/reboot-std-api-0.46.0.tgz", - "integrity": "sha512-SPt7RsLdJoGxxrf/896yC7/lqBNqVmAPL2BFpZbyCV+Bb205VAodm7ERylhvaFEU7PgucKOrjR7WXXSTeXDo6w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-api/-/reboot-std-api-1.0.3.tgz", + "integrity": "sha512-k6aRTEuL40ki4FN51A7UdkvGK2bAOJ5npStWkL3tpLhIwtmcmdtbNZZeYEdVgPCLEgReIep9bd61U6ObnNzXtA==", "requires": { "@scarf/scarf": "1.4.0" } }, "@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "requires": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -9245,9 +8808,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -9386,30 +8949,6 @@ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==" }, - "cacache": { - "version": "20.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", - "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", - "requires": { - "@npmcli/fs": "^5.0.0", - "fs-minipass": "^3.0.0", - "glob": "^13.0.0", - "lru-cache": "^11.1.0", - "minipass": "^7.0.3", - "minipass-collect": "^2.0.1", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "p-map": "^7.0.2", - "ssri": "^13.0.0" - }, - "dependencies": { - "lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==" - } - } - }, "call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -9499,9 +9038,9 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==" }, "content-type": { "version": "1.0.5", @@ -9899,9 +9438,9 @@ } }, "eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==" }, "expect-type": { "version": "1.2.2", @@ -9949,9 +9488,9 @@ } }, "express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "requires": { "ip-address": "10.1.0" } @@ -10081,14 +9620,6 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==" }, - "fs-minipass": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "requires": { - "minipass": "^7.0.3" - } - }, "fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -10144,44 +9675,6 @@ "resolve-pkg-maps": "^1.0.0" } }, - "glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "requires": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "dependencies": { - "balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" - }, - "brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "requires": { - "balanced-match": "^4.0.2" - } - }, - "minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "requires": { - "brace-expansion": "^5.0.5" - } - }, - "minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==" - } - } - }, "glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -10250,9 +9743,9 @@ } }, "hono": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", - "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==" + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==" }, "html-encoding-sniffer": { "version": "4.0.0", @@ -10262,11 +9755,6 @@ "whatwg-encoding": "^3.1.1" } }, - "http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==" - }, "http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -10514,9 +10002,9 @@ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==" }, "jose": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", - "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==" + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==" }, "js-sha1": { "version": "0.7.0", @@ -10746,25 +10234,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "make-fetch-happen": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.5.tgz", - "integrity": "sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg==", - "requires": { - "@gar/promise-retry": "^1.0.0", - "@npmcli/agent": "^4.0.0", - "@npmcli/redact": "^4.0.0", - "cacache": "^20.0.1", - "http-cache-semantics": "^4.1.1", - "minipass": "^7.0.2", - "minipass-fetch": "^5.0.0", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^1.0.0", - "proc-log": "^6.0.0", - "ssri": "^13.0.0" - } - }, "math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -10820,90 +10289,6 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" }, - "minipass-collect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", - "requires": { - "minipass": "^7.0.3" - } - }, - "minipass-fetch": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", - "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", - "requires": { - "iconv-lite": "^0.7.2", - "minipass": "^7.0.3", - "minipass-sized": "^2.0.0", - "minizlib": "^3.0.1" - }, - "dependencies": { - "iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - } - } - }, - "minipass-flush": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", - "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", - "requires": { - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "requires": { - "yallist": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } - }, - "minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "requires": { - "minipass": "^3.0.0" - }, - "dependencies": { - "minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "requires": { - "yallist": "^4.0.0" - } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } - }, - "minipass-sized": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", - "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", - "requires": { - "minipass": "^7.1.2" - } - }, "minizlib": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", @@ -10938,19 +10323,19 @@ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==" }, "node-gyp": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz", - "integrity": "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.3.0.tgz", + "integrity": "sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==", "requires": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^15.0.0", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", + "undici": "^6.25.0", "which": "^6.0.0" }, "dependencies": { @@ -11074,11 +10459,6 @@ "p-limit": "^3.0.2" } }, - "p-map": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==" - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11110,26 +10490,10 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, - "path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "requires": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "dependencies": { - "lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==" - } - } - }, "path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==" + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==" }, "pathe": { "version": "2.0.3", @@ -11213,9 +10577,9 @@ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, "qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "requires": { "side-channel": "^1.1.0" } @@ -11523,43 +10887,11 @@ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==" }, - "smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==" - }, - "socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "requires": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - } - }, - "socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "requires": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - } - }, "source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" }, - "ssri": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", - "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", - "requires": { - "minipass": "^7.0.3" - } - }, "stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -11795,6 +11127,11 @@ "@typescript-eslint/utils": "8.46.0" } }, + "undici": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.25.0.tgz", + "integrity": "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -12039,9 +11376,9 @@ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==" }, "zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "requires": {} } } diff --git a/reboot/examples/bank-pydantic/web/package.json b/reboot/examples/bank-pydantic/web/package.json index 4bb1cdb0..b698b356 100644 --- a/reboot/examples/bank-pydantic/web/package.json +++ b/reboot/examples/bank-pydantic/web/package.json @@ -5,9 +5,9 @@ "type": "module", "dependencies": { "@eslint/js": "^9.34.0", - "@reboot-dev/reboot-react": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-std": "0.46.0", + "@reboot-dev/reboot-react": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-std": "1.0.3", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/eslint__js": "^8.42.3", diff --git a/reboot/examples/bank-zod/.rbtrc b/reboot/examples/bank-zod/.rbtrc index bbfe61ab..4fbe542f 100644 --- a/reboot/examples/bank-zod/.rbtrc +++ b/reboot/examples/bank-zod/.rbtrc @@ -12,10 +12,10 @@ generate --react-extensions dev run --watch=backend/src/**/*.ts -dev run --name=bank-zod +dev run --application-name=bank-zod dev run --nodejs dev run --application=backend/src/main.ts -dev expunge --name=bank-zod +dev expunge --application-name=bank-zod diff --git a/reboot/examples/bank-zod/package-lock.json b/reboot/examples/bank-zod/package-lock.json index 1882821a..ebbb8133 100644 --- a/reboot/examples/bank-zod/package-lock.json +++ b/reboot/examples/bank-zod/package-lock.json @@ -9,10 +9,10 @@ "version": "0.1.0", "dependencies": { "@bufbuild/protobuf": "1.10.1", - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-react": "0.46.0", - "@reboot-dev/reboot-std": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", + "@reboot-dev/reboot-std": "1.0.3", "@tailwindcss/vite": "^4.1.11", "@types/node": "20.11.5", "lucide-react": "^0.525.0", @@ -894,9 +894,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -1025,15 +1025,18 @@ } }, "node_modules/@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "license": "MIT", "workspaces": [ "examples/*" ], + "engines": { + "node": ">=20" + }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" @@ -1048,9 +1051,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -1088,9 +1091,9 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1196,15 +1199,15 @@ } }, "node_modules/@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -1233,9 +1236,9 @@ } }, "node_modules/@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0", @@ -1259,15 +1262,15 @@ } }, "node_modules/@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", "license": "Apache-2.0", "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -1294,32 +1297,32 @@ } }, "node_modules/@reboot-dev/reboot-std": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std/-/reboot-std-0.46.0.tgz", - "integrity": "sha512-Sd915ak5YKuLcEteA/ULzJsehiDp3/TSrPHXIw1Osza9Yqakewntv47QsLgTnqCBxhpvQ49WbTqK6iEJQ1Sgqw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std/-/reboot-std-1.0.3.tgz", + "integrity": "sha512-+N6yWU6uVXnJikK8ECh3NuiGo47zfP8ThE34SJCyXzSSJQS7suf9sS2yLCNaWbSKt7vTOrx9clAF9JjEnhoF7Q==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-std-api": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-std-api": "1.0.3", "@scarf/scarf": "1.4.0" } }, "node_modules/@reboot-dev/reboot-std-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-api/-/reboot-std-api-0.46.0.tgz", - "integrity": "sha512-SPt7RsLdJoGxxrf/896yC7/lqBNqVmAPL2BFpZbyCV+Bb205VAodm7ERylhvaFEU7PgucKOrjR7WXXSTeXDo6w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-api/-/reboot-std-api-1.0.3.tgz", + "integrity": "sha512-k6aRTEuL40ki4FN51A7UdkvGK2bAOJ5npStWkL3tpLhIwtmcmdtbNZZeYEdVgPCLEgReIep9bd61U6ObnNzXtA==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0" } }, "node_modules/@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -2356,9 +2359,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -3122,9 +3125,9 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -3179,9 +3182,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "license": "MIT", "dependencies": { "ip-address": "10.1.0" @@ -3610,9 +3613,9 @@ } }, "node_modules/hono": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", - "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==", + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -3808,9 +3811,9 @@ } }, "node_modules/jose": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", - "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -6676,12 +6679,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", "peerDependencies": { - "zod": "^3.25 || ^4" + "zod": "^3.25.28 || ^4" } } }, @@ -7174,9 +7177,9 @@ } }, "@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "requires": {} }, "@humanfs/core": { @@ -7265,15 +7268,15 @@ } }, "@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "requires": {} }, "@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "requires": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -7295,9 +7298,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7379,13 +7382,13 @@ "optional": true }, "@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "requires": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -7402,9 +7405,9 @@ } }, "@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "requires": { "@scarf/scarf": "1.4.0", "typescript": "5.4.5", @@ -7419,14 +7422,14 @@ } }, "@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", - "requires": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", + "requires": { + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -7443,29 +7446,29 @@ } }, "@reboot-dev/reboot-std": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std/-/reboot-std-0.46.0.tgz", - "integrity": "sha512-Sd915ak5YKuLcEteA/ULzJsehiDp3/TSrPHXIw1Osza9Yqakewntv47QsLgTnqCBxhpvQ49WbTqK6iEJQ1Sgqw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std/-/reboot-std-1.0.3.tgz", + "integrity": "sha512-+N6yWU6uVXnJikK8ECh3NuiGo47zfP8ThE34SJCyXzSSJQS7suf9sS2yLCNaWbSKt7vTOrx9clAF9JjEnhoF7Q==", "requires": { - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-std-api": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-std-api": "1.0.3", "@scarf/scarf": "1.4.0" } }, "@reboot-dev/reboot-std-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-api/-/reboot-std-api-0.46.0.tgz", - "integrity": "sha512-SPt7RsLdJoGxxrf/896yC7/lqBNqVmAPL2BFpZbyCV+Bb205VAodm7ERylhvaFEU7PgucKOrjR7WXXSTeXDo6w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-api/-/reboot-std-api-1.0.3.tgz", + "integrity": "sha512-k6aRTEuL40ki4FN51A7UdkvGK2bAOJ5npStWkL3tpLhIwtmcmdtbNZZeYEdVgPCLEgReIep9bd61U6ObnNzXtA==", "requires": { "@scarf/scarf": "1.4.0" } }, "@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "requires": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -8098,9 +8101,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -8627,9 +8630,9 @@ } }, "eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==" }, "exponential-backoff": { "version": "3.1.2", @@ -8672,9 +8675,9 @@ } }, "express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "requires": { "ip-address": "10.1.0" }, @@ -8968,9 +8971,9 @@ } }, "hono": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", - "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==" + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==" }, "http-cache-semantics": { "version": "4.2.0", @@ -9107,9 +9110,9 @@ "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==" }, "jose": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", - "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==" + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==" }, "js-sha1": { "version": "0.7.0", @@ -10758,9 +10761,9 @@ "integrity": "sha512-J8poo92VuhKjNknViHRAIuuN6li/EwFbAC8OedzI8uxpEPGiXHGQu9wemIAioIpqgfB4SySaJhdk0mH5Y4ICBg==" }, "zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "requires": {} } } diff --git a/reboot/examples/bank-zod/package.json b/reboot/examples/bank-zod/package.json index ebe7f068..13fcaa67 100644 --- a/reboot/examples/bank-zod/package.json +++ b/reboot/examples/bank-zod/package.json @@ -9,10 +9,10 @@ }, "dependencies": { "@bufbuild/protobuf": "1.10.1", - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-std": "0.46.0", - "@reboot-dev/reboot-react": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-std": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", "@tailwindcss/vite": "^4.1.11", "@types/node": "20.11.5", "lucide-react": "^0.525.0", diff --git a/reboot/examples/bank/.rbtrc b/reboot/examples/bank/.rbtrc index 602d8977..03b07bb4 100644 --- a/reboot/examples/bank/.rbtrc +++ b/reboot/examples/bank/.rbtrc @@ -12,10 +12,10 @@ dev run --watch=backend/**/*.py dev run --python # Save state between chaos restarts. -dev run --name=bank +dev run --application-name=bank # Run the application! dev run --application=backend/src/main.py # When expunging, expunge that state we've saved. -dev expunge --name=bank +dev expunge --application-name=bank diff --git a/reboot/examples/bank/README.md b/reboot/examples/bank/README.md index 22d8eb68..70db3074 100644 --- a/reboot/examples/bank/README.md +++ b/reboot/examples/bank/README.md @@ -10,12 +10,12 @@ For the impatient: This repository contains an example bank written using Reboot. -The [Reboot '.proto' definitions](https://docs.reboot.dev/develop/define/overview/#code-generation) +The [Reboot '.proto' definitions](https://docs.reboot.dev/learn_more/define/overview#code-generation) can be found in the `api/` directory, grouped into subdirectories by proto package, while backend specific code can be found in `backend/` and front end specific code in `web/`. -_For more information on all of the Reboot examples, please [see the docs](https://docs.reboot.dev/get_started/examples)._ +_For more information on all of the Reboot examples, please [see the docs](https://docs.reboot.dev/full_stack_apps/examples)._ ## Prepare an environment by... @@ -78,7 +78,7 @@ API key as an environment variable. Replace `MY_MAILGUN_API_KEY` with your own mailgun API key, which you can get from [your Mailgun account](https://www.mailgun.com): ```shell -export RBT_SECRET_MAILGUN_API_KEY="MY_MAILGUN_API_KEY" +export MAILGUN_API_KEY="MY_MAILGUN_API_KEY" ``` #### Run the backend diff --git a/reboot/examples/bank/backend/src/main.py b/reboot/examples/bank/backend/src/main.py index 9714e18b..e5fca3ac 100644 --- a/reboot/examples/bank/backend/src/main.py +++ b/reboot/examples/bank/backend/src/main.py @@ -1,4 +1,5 @@ import asyncio +import os import random import reboot.thirdparty.mailgun from bank.v1.bank_rbt import ( @@ -37,9 +38,8 @@ WriterContext, ) from reboot.aio.external import InitializeContext -from reboot.aio.secrets import SecretNotFoundException, Secrets from reboot.std.collections.v1.sorted_map import SortedMap, sorted_map_library -from reboot.thirdparty.mailgun import MAILGUN_API_KEY_SECRET_NAME +from reboot.thirdparty.mailgun import ENVVAR_MAILGUN_API_KEY from typing import Optional from uuid import uuid4 from uuid7 import create as uuid7 @@ -112,7 +112,6 @@ class BankServicer(Bank.Servicer): def __init__(self): self._html_email = open('backend/src/email_to_bank_users.html').read() self._text_email = open('backend/src/email_to_bank_users.txt').read() - self._secrets = Secrets() def authorizer(self): return allow() @@ -203,15 +202,14 @@ async def transfer( return TransferResponse() async def _mailgun_api_key(self) -> Optional[str]: - try: - secret_bytes = await self._secrets.get(MAILGUN_API_KEY_SECRET_NAME) - return secret_bytes.decode() - except SecretNotFoundException: + api_key = os.environ.get(ENVVAR_MAILGUN_API_KEY) + if api_key is None: logger.warning( - "The Mailgun API key secret is not set: please see the README to " - "enable sending email." + "The Mailgun API key secret is not set: " + "please see the README to enable sending " + "email." ) - return None + return api_key async def initialize(context: InitializeContext): diff --git a/reboot/examples/bank/pyproject.toml b/reboot/examples/bank/pyproject.toml index 6f2e8e16..9500e02e 100644 --- a/reboot/examples/bank/pyproject.toml +++ b/reboot/examples/bank/pyproject.toml @@ -1,14 +1,14 @@ [project] requires-python = ">= 3.10" dependencies = [ - "reboot==0.46.0", + "reboot==1.0.3", ] [tool.rye] dev-dependencies = [ "mypy==1.18.1", "types-protobuf>=4.24.0.20240129", - "reboot==0.46.0", + "reboot==1.0.3", ] # This project only uses `rye` to provide `python` and its dependencies, so diff --git a/reboot/examples/bank/requirements-dev.lock b/reboot/examples/bank/requirements-dev.lock index 74fb6107..78841234 100644 --- a/reboot/examples/bank/requirements-dev.lock +++ b/reboot/examples/bank/requirements-dev.lock @@ -52,15 +52,20 @@ deprecated==1.2.15 # via opentelemetry-semantic-conventions exceptiongroup==1.2.2 # via anyio + # via pydantic-ai-slim fastapi==0.115.12 # via reboot frozenlist==1.4.1 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -89,7 +94,10 @@ hpack==4.1.0 httpcore==1.0.8 # via httpx httpx==0.28.1 + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 @@ -111,9 +119,11 @@ jsonschema-specifications==2025.9.1 # via jsonschema kubernetes-asyncio==31.1.0 # via reboot +logfire-api==4.32.1 + # via pydantic-graph markupsafe==2.1.5 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot multidict==6.1.0 # via aiohttp @@ -129,6 +139,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -172,11 +183,18 @@ pycparser==2.22 # via cffi pydantic==2.12.5 # via fastapi + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot +pydantic-ai-slim==1.87.0 + # via reboot pydantic-core==2.41.5 # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim pydantic-settings==2.13.1 # via mcp pyjwt==2.10.1 @@ -195,7 +213,7 @@ python-ulid==3.1.0 pyyaml==6.0.2 # via kubernetes-asyncio # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications @@ -231,12 +249,15 @@ typing-extensions==4.15.0 # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via typing-inspection # via uvicorn typing-inspection==0.4.2 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot diff --git a/reboot/examples/bank/requirements.lock b/reboot/examples/bank/requirements.lock index 29a12988..e9cd6645 100644 --- a/reboot/examples/bank/requirements.lock +++ b/reboot/examples/bank/requirements.lock @@ -52,15 +52,20 @@ deprecated==1.2.15 # via opentelemetry-semantic-conventions exceptiongroup==1.2.2 # via anyio + # via pydantic-ai-slim fastapi==0.115.12 # via reboot frozenlist==1.4.1 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -89,7 +94,10 @@ hpack==4.1.0 httpcore==1.0.8 # via httpx httpx==0.28.1 + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 @@ -111,9 +119,11 @@ jsonschema-specifications==2025.9.1 # via jsonschema kubernetes-asyncio==31.1.0 # via reboot +logfire-api==4.32.1 + # via pydantic-graph markupsafe==2.1.5 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot multidict==6.1.0 # via aiohttp @@ -126,6 +136,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -168,11 +179,18 @@ pycparser==2.22 # via cffi pydantic==2.12.5 # via fastapi + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot +pydantic-ai-slim==1.87.0 + # via reboot pydantic-core==2.41.5 # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim pydantic-settings==2.13.1 # via mcp pyjwt==2.10.1 @@ -191,7 +209,7 @@ python-ulid==3.1.0 pyyaml==6.0.2 # via kubernetes-asyncio # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications @@ -224,12 +242,15 @@ typing-extensions==4.15.0 # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via typing-inspection # via uvicorn typing-inspection==0.4.2 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot diff --git a/reboot/examples/bank/web/package-lock.json b/reboot/examples/bank/web/package-lock.json index 656423f0..d93cd89c 100644 --- a/reboot/examples/bank/web/package-lock.json +++ b/reboot/examples/bank/web/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@bufbuild/protobuf": "1.10.1", "@eslint/js": "^9.34.0", - "@reboot-dev/reboot-react": "0.46.0", + "@reboot-dev/reboot-react": "1.0.3", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/eslint__js": "^8.42.3", @@ -996,9 +996,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -1104,15 +1104,18 @@ } }, "node_modules/@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "license": "MIT", "workspaces": [ "examples/*" ], + "engines": { + "node": ">=20" + }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" @@ -1127,9 +1130,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -1167,9 +1170,9 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1224,9 +1227,9 @@ } }, "node_modules/@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0", @@ -1260,15 +1263,15 @@ } }, "node_modules/@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", "license": "Apache-2.0", "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -1295,12 +1298,12 @@ } }, "node_modules/@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -2270,9 +2273,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -2623,9 +2626,9 @@ "license": "MIT" }, "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "license": "MIT", "engines": { "node": ">=18" @@ -3201,9 +3204,9 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -3262,9 +3265,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "license": "MIT", "dependencies": { "ip-address": "10.1.0" @@ -3656,9 +3659,9 @@ } }, "node_modules/hono": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", - "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==", + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -4059,9 +4062,9 @@ "license": "ISC" }, "node_modules/jose": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", - "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -4602,9 +4605,9 @@ } }, "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", "funding": { "type": "opencollective", @@ -4745,9 +4748,9 @@ } }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -5945,12 +5948,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", "peerDependencies": { - "zod": "^3.25 || ^4" + "zod": "^3.25.28 || ^4" } } }, @@ -6433,9 +6436,9 @@ } }, "@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "requires": {} }, "@humanfs/core": { @@ -6498,15 +6501,15 @@ } }, "@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "requires": {} }, "@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "requires": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -6528,9 +6531,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6569,9 +6572,9 @@ } }, "@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "requires": { "@scarf/scarf": "1.4.0", "typescript": "5.4.5", @@ -6591,14 +6594,14 @@ } }, "@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", - "requires": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", + "requires": { + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -6615,11 +6618,11 @@ } }, "@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "requires": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -7202,9 +7205,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7422,9 +7425,9 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==" }, "content-type": { "version": "1.0.5", @@ -7803,9 +7806,9 @@ } }, "eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==" }, "expect-type": { "version": "1.2.2", @@ -7848,9 +7851,9 @@ } }, "express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "requires": { "ip-address": "10.1.0" } @@ -8091,9 +8094,9 @@ } }, "hono": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", - "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==" + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==" }, "html-encoding-sniffer": { "version": "4.0.0", @@ -8337,9 +8340,9 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "jose": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", - "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==" + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==" }, "js-sha1": { "version": "0.7.0", @@ -8684,9 +8687,9 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==" + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==" }, "pathe": { "version": "2.0.3", @@ -8765,9 +8768,9 @@ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, "qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "requires": { "side-channel": "^1.1.0" } @@ -9461,9 +9464,9 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==" }, "zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "requires": {} } } diff --git a/reboot/examples/bank/web/package.json b/reboot/examples/bank/web/package.json index e5854724..981b7a46 100644 --- a/reboot/examples/bank/web/package.json +++ b/reboot/examples/bank/web/package.json @@ -6,7 +6,7 @@ "dependencies": { "@bufbuild/protobuf": "1.10.1", "@eslint/js": "^9.34.0", - "@reboot-dev/reboot-react": "0.46.0", + "@reboot-dev/reboot-react": "1.0.3", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/eslint__js": "^8.42.3", diff --git a/reboot/examples/bank_README.md.yaml b/reboot/examples/bank_README.md.yaml index 83cffec4..cd1ef9fb 100644 --- a/reboot/examples/bank_README.md.yaml +++ b/reboot/examples/bank_README.md.yaml @@ -17,5 +17,5 @@ extra_setup: | Replace `MY_MAILGUN_API_KEY` with your own mailgun API key, which you can get from [your Mailgun account](https://www.mailgun.com): ```shell - export RBT_SECRET_MAILGUN_API_KEY="MY_MAILGUN_API_KEY" + export MAILGUN_API_KEY="MY_MAILGUN_API_KEY" ``` diff --git a/reboot/examples/boutique/.rbtrc b/reboot/examples/boutique/.rbtrc index 88216c3a..b70b54e4 100644 --- a/reboot/examples/boutique/.rbtrc +++ b/reboot/examples/boutique/.rbtrc @@ -41,11 +41,11 @@ dev run --python serve run --python # Set the application name for commands that require it. -cloud up --name=boutique -cloud down --name=boutique --expunge -dev expunge --name=boutique -dev run --name=boutique -serve run --name=boutique +cloud up --application-name=boutique +cloud down --application-name=boutique --expunge +dev expunge --application-name=boutique +dev run --application-name=boutique +serve run --application-name=boutique # Run the application! dev run --application=backend/src/main.py diff --git a/reboot/examples/boutique/Dockerfile b/reboot/examples/boutique/Dockerfile index 13bf3eb5..bb421aaa 100644 --- a/reboot/examples/boutique/Dockerfile +++ b/reboot/examples/boutique/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/reboot-dev/reboot-base:0.46.0 +FROM ghcr.io/reboot-dev/reboot-base:1.0.3 WORKDIR /app diff --git a/reboot/examples/boutique/README.md b/reboot/examples/boutique/README.md index 0106d3a9..45251d29 100644 --- a/reboot/examples/boutique/README.md +++ b/reboot/examples/boutique/README.md @@ -14,12 +14,12 @@ It was originally forked from [GoogleCloudPlatform/microservices-demo](https://github.com/GoogleCloudPlatform/microservices-demo), and demonstrates how Reboot makes it simple to write correct microservice applications. -The [Reboot '.proto' definitions](https://docs.reboot.dev/develop/define/overview/#code-generation) +The [Reboot '.proto' definitions](https://docs.reboot.dev/learn_more/define/overview#code-generation) can be found in the `api/` directory, grouped into subdirectories by proto package, while backend specific code can be found in `backend/` and front end specific code in `web/`. -_For more information on all of the Reboot examples, please [see the docs](https://docs.reboot.dev/get_started/examples)._ +_For more information on all of the Reboot examples, please [see the docs](https://docs.reboot.dev/full_stack_apps/examples)._ ## Prepare an environment by... @@ -82,7 +82,7 @@ API key as an environment variable. Replace `MY_MAILGUN_API_KEY` with your own mailgun API key, which you can get from [your Mailgun account](https://www.mailgun.com): ```shell -export RBT_SECRET_MAILGUN_API_KEY="MY_MAILGUN_API_KEY" +export MAILGUN_API_KEY="MY_MAILGUN_API_KEY" ``` #### Run the backend diff --git a/reboot/examples/boutique/backend/src/checkout/servicer.py b/reboot/examples/boutique/backend/src/checkout/servicer.py index 0635ffca..65145c4d 100644 --- a/reboot/examples/boutique/backend/src/checkout/servicer.py +++ b/reboot/examples/boutique/backend/src/checkout/servicer.py @@ -13,16 +13,12 @@ TransactionContext, WriterContext, ) -from reboot.aio.secrets import SecretNotFoundException, Secrets -from reboot.thirdparty.mailgun import MAILGUN_API_KEY_SECRET_NAME +from reboot.thirdparty.mailgun import ENVVAR_MAILGUN_API_KEY from typing import Optional class CheckoutServicer(Checkout.Servicer): - def __init__(self): - self._secrets = Secrets() - def authorizer(self): return allow() @@ -132,12 +128,11 @@ async def orders( return demo_pb2.OrdersResponse(orders=reversed(self.state.orders)) async def _mailgun_api_key(self) -> Optional[str]: - try: - secret_bytes = await self._secrets.get(MAILGUN_API_KEY_SECRET_NAME) - return secret_bytes.decode() - except SecretNotFoundException: + api_key = os.environ.get(ENVVAR_MAILGUN_API_KEY) + if api_key is None: logger.warning( - "The Mailgun API key secret is not set: please see the README to " - "enable sending email." + "The Mailgun API key secret is not set: " + "please see the README to enable sending " + "email." ) - return None + return api_key diff --git a/reboot/examples/boutique/backend/tests/full_app_test.py b/reboot/examples/boutique/backend/tests/full_app_test.py index a65f320e..0d5c1724 100644 --- a/reboot/examples/boutique/backend/tests/full_app_test.py +++ b/reboot/examples/boutique/backend/tests/full_app_test.py @@ -1,4 +1,5 @@ import asyncio +import os import unittest from boutique.v1 import demo_pb2, demo_pb2_grpc from boutique.v1.demo_rbt import Cart, Checkout, Shipping @@ -9,10 +10,8 @@ from main import initialize from productcatalog.servicer import ProductCatalogServicer from reboot.aio.applications import Application -from reboot.aio.secrets import MockSecretSource, Secrets from reboot.aio.tests import Reboot from reboot.aio.types import ServiceName -from reboot.thirdparty.mailgun import MAILGUN_API_KEY_SECRET_NAME from reboot.thirdparty.mailgun.servicers import MockMessageServicer from shipping.servicer import ShippingServicer @@ -23,13 +22,7 @@ class TestCase(unittest.IsolatedAsyncioTestCase): async def asyncSetUp(self) -> None: - Secrets.set_secret_source( - MockSecretSource( - { - MAILGUN_API_KEY_SECRET_NAME: MAILGUN_API_KEY.encode(), - } - ) - ) + os.environ["MAILGUN_API_KEY"] = MAILGUN_API_KEY self.rbt = Reboot() servicers: list[type] = [ diff --git a/reboot/examples/boutique/pyproject.toml b/reboot/examples/boutique/pyproject.toml index 142168e1..1c50821b 100644 --- a/reboot/examples/boutique/pyproject.toml +++ b/reboot/examples/boutique/pyproject.toml @@ -1,7 +1,7 @@ [project] requires-python = ">= 3.10" dependencies = [ - "reboot==0.46.0", + "reboot==1.0.3", ] [tool.rye] @@ -9,7 +9,7 @@ dev-dependencies = [ "mypy==1.18.1", "pytest>=7.4.2", "types-protobuf>=4.24.0.20240129", - "reboot==0.46.0", + "reboot==1.0.3", ] # This project only uses `rye` to provide `python` and its dependencies, so diff --git a/reboot/examples/boutique/requirements-dev.lock b/reboot/examples/boutique/requirements-dev.lock index 31a9f3ca..8b72ef23 100644 --- a/reboot/examples/boutique/requirements-dev.lock +++ b/reboot/examples/boutique/requirements-dev.lock @@ -52,16 +52,21 @@ deprecated==1.2.15 # via opentelemetry-semantic-conventions exceptiongroup==1.2.2 # via anyio + # via pydantic-ai-slim # via pytest fastapi==0.115.12 # via reboot frozenlist==1.4.1 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -90,7 +95,10 @@ hpack==4.1.0 httpcore==1.0.8 # via httpx httpx==0.28.1 + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 @@ -114,9 +122,11 @@ jsonschema-specifications==2025.9.1 # via jsonschema kubernetes-asyncio==31.1.0 # via reboot +logfire-api==4.32.1 + # via pydantic-graph markupsafe==2.1.5 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot multidict==6.1.0 # via aiohttp @@ -132,6 +142,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -178,11 +189,18 @@ pycparser==2.22 # via cffi pydantic==2.12.5 # via fastapi + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot +pydantic-ai-slim==1.87.0 + # via reboot pydantic-core==2.41.5 # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim pydantic-settings==2.13.1 # via mcp pyjwt==2.10.1 @@ -202,7 +220,7 @@ python-ulid==3.1.0 pyyaml==6.0.2 # via kubernetes-asyncio # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications @@ -239,12 +257,15 @@ typing-extensions==4.15.0 # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via typing-inspection # via uvicorn typing-inspection==0.4.2 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot diff --git a/reboot/examples/boutique/requirements.lock b/reboot/examples/boutique/requirements.lock index 29a12988..e9cd6645 100644 --- a/reboot/examples/boutique/requirements.lock +++ b/reboot/examples/boutique/requirements.lock @@ -52,15 +52,20 @@ deprecated==1.2.15 # via opentelemetry-semantic-conventions exceptiongroup==1.2.2 # via anyio + # via pydantic-ai-slim fastapi==0.115.12 # via reboot frozenlist==1.4.1 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -89,7 +94,10 @@ hpack==4.1.0 httpcore==1.0.8 # via httpx httpx==0.28.1 + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 @@ -111,9 +119,11 @@ jsonschema-specifications==2025.9.1 # via jsonschema kubernetes-asyncio==31.1.0 # via reboot +logfire-api==4.32.1 + # via pydantic-graph markupsafe==2.1.5 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot multidict==6.1.0 # via aiohttp @@ -126,6 +136,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -168,11 +179,18 @@ pycparser==2.22 # via cffi pydantic==2.12.5 # via fastapi + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot +pydantic-ai-slim==1.87.0 + # via reboot pydantic-core==2.41.5 # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim pydantic-settings==2.13.1 # via mcp pyjwt==2.10.1 @@ -191,7 +209,7 @@ python-ulid==3.1.0 pyyaml==6.0.2 # via kubernetes-asyncio # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications @@ -224,12 +242,15 @@ typing-extensions==4.15.0 # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via typing-inspection # via uvicorn typing-inspection==0.4.2 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot diff --git a/reboot/examples/boutique/web/package-lock.json b/reboot/examples/boutique/web/package-lock.json index d6e356e5..202282a8 100644 --- a/reboot/examples/boutique/web/package-lock.json +++ b/reboot/examples/boutique/web/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@bufbuild/protobuf": "1.10.1", "@eslint/js": "^9.34.0", - "@reboot-dev/reboot-react": "0.46.0", + "@reboot-dev/reboot-react": "1.0.3", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/eslint__js": "^8.42.3", @@ -997,9 +997,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -1104,15 +1104,18 @@ } }, "node_modules/@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "license": "MIT", "workspaces": [ "examples/*" ], + "engines": { + "node": ">=20" + }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" @@ -1127,9 +1130,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -1167,9 +1170,9 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1224,9 +1227,9 @@ } }, "node_modules/@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0", @@ -1260,15 +1263,15 @@ } }, "node_modules/@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", "license": "Apache-2.0", "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -1295,12 +1298,12 @@ } }, "node_modules/@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -2278,9 +2281,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -2635,9 +2638,9 @@ "license": "MIT" }, "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "license": "MIT", "engines": { "node": ">=18" @@ -3212,9 +3215,9 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -3273,9 +3276,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "license": "MIT", "dependencies": { "ip-address": "10.1.0" @@ -3650,9 +3653,9 @@ } }, "node_modules/hono": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", - "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==", + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -4053,9 +4056,9 @@ "license": "ISC" }, "node_modules/jose": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", - "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -4582,9 +4585,9 @@ } }, "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", "funding": { "type": "opencollective", @@ -4725,9 +4728,9 @@ } }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -6025,12 +6028,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", "peerDependencies": { - "zod": "^3.25 || ^4" + "zod": "^3.25.28 || ^4" } } }, @@ -6513,9 +6516,9 @@ } }, "@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "requires": {} }, "@humanfs/core": { @@ -6578,15 +6581,15 @@ } }, "@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "requires": {} }, "@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "requires": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -6608,9 +6611,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6649,9 +6652,9 @@ } }, "@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "requires": { "@scarf/scarf": "1.4.0", "typescript": "5.4.5", @@ -6671,14 +6674,14 @@ } }, "@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", - "requires": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", + "requires": { + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -6695,11 +6698,11 @@ } }, "@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "requires": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -7287,9 +7290,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7507,9 +7510,9 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==" }, "content-type": { "version": "1.0.5", @@ -7888,9 +7891,9 @@ } }, "eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==" }, "expect-type": { "version": "1.2.2", @@ -7933,9 +7936,9 @@ } }, "express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "requires": { "ip-address": "10.1.0" } @@ -8170,9 +8173,9 @@ } }, "hono": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", - "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==" + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==" }, "html-encoding-sniffer": { "version": "4.0.0", @@ -8416,9 +8419,9 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "jose": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", - "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==" + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==" }, "js-sha1": { "version": "0.7.0", @@ -8756,9 +8759,9 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==" + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==" }, "pathe": { "version": "2.0.3", @@ -8837,9 +8840,9 @@ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, "qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "requires": { "side-channel": "^1.1.0" } @@ -9583,9 +9586,9 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==" }, "zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "requires": {} } } diff --git a/reboot/examples/boutique/web/package.json b/reboot/examples/boutique/web/package.json index 34da53bb..c1bba017 100644 --- a/reboot/examples/boutique/web/package.json +++ b/reboot/examples/boutique/web/package.json @@ -6,7 +6,7 @@ "dependencies": { "@bufbuild/protobuf": "1.10.1", "@eslint/js": "^9.34.0", - "@reboot-dev/reboot-react": "0.46.0", + "@reboot-dev/reboot-react": "1.0.3", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/eslint__js": "^8.42.3", diff --git a/reboot/examples/boutique_README.md.yaml b/reboot/examples/boutique_README.md.yaml index 6075f598..ab3d7f4c 100644 --- a/reboot/examples/boutique_README.md.yaml +++ b/reboot/examples/boutique_README.md.yaml @@ -21,5 +21,5 @@ extra_setup: | Replace `MY_MAILGUN_API_KEY` with your own mailgun API key, which you can get from [your Mailgun account](https://www.mailgun.com): ```shell - export RBT_SECRET_MAILGUN_API_KEY="MY_MAILGUN_API_KEY" + export MAILGUN_API_KEY="MY_MAILGUN_API_KEY" ``` diff --git a/reboot/examples/chat-room-nodejs/.rbtrc b/reboot/examples/chat-room-nodejs/.rbtrc index 479e0ac6..ed1e5271 100644 --- a/reboot/examples/chat-room-nodejs/.rbtrc +++ b/reboot/examples/chat-room-nodejs/.rbtrc @@ -31,15 +31,15 @@ generate api/ # See .tests/test.sh generate --nodejs-extensions -dev run --name=chat-room-nodejs +dev run --application-name=chat-room-nodejs dev run --nodejs dev run --application=backend/src/main.ts -dev expunge --name=chat-room-nodejs +dev expunge --application-name=chat-room-nodejs -serve run --name=chat-room +serve run --application-name=chat-room # Tell `rbt serve` that this is a Node.js application. serve run --nodejs diff --git a/reboot/examples/chat-room-nodejs/Dockerfile b/reboot/examples/chat-room-nodejs/Dockerfile index 70ca4727..77fc37cf 100644 --- a/reboot/examples/chat-room-nodejs/Dockerfile +++ b/reboot/examples/chat-room-nodejs/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/reboot-dev/reboot-base:0.46.0 +FROM ghcr.io/reboot-dev/reboot-base:1.0.3 WORKDIR /app diff --git a/reboot/examples/chat-room-nodejs/package-lock.json b/reboot/examples/chat-room-nodejs/package-lock.json index 6cba2031..03a39322 100644 --- a/reboot/examples/chat-room-nodejs/package-lock.json +++ b/reboot/examples/chat-room-nodejs/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@bufbuild/protobuf": "1.10.1", - "@reboot-dev/reboot": "0.46.0", + "@reboot-dev/reboot": "1.0.3", "@types/node": "20.11.5", "typescript": "5.4.5" }, @@ -509,15 +509,15 @@ } }, "node_modules/@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -546,9 +546,9 @@ } }, "node_modules/@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0", @@ -3118,13 +3118,13 @@ "optional": true }, "@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "requires": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -3141,9 +3141,9 @@ } }, "@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "requires": { "@scarf/scarf": "1.4.0", "typescript": "5.4.5", diff --git a/reboot/examples/chat-room-nodejs/package.json b/reboot/examples/chat-room-nodejs/package.json index f9d1f668..b9ff39cb 100644 --- a/reboot/examples/chat-room-nodejs/package.json +++ b/reboot/examples/chat-room-nodejs/package.json @@ -5,7 +5,7 @@ "type": "module", "dependencies": { "@bufbuild/protobuf": "1.10.1", - "@reboot-dev/reboot": "0.46.0", + "@reboot-dev/reboot": "1.0.3", "typescript": "5.4.5", "@types/node": "20.11.5" }, diff --git a/reboot/examples/chat-room/.rbtrc b/reboot/examples/chat-room/.rbtrc index f7461dc8..a56f5e2d 100644 --- a/reboot/examples/chat-room/.rbtrc +++ b/reboot/examples/chat-room/.rbtrc @@ -41,11 +41,11 @@ dev run --watch=backend/src/**/*.py dev run --python # Set the application name for commands that require it. -cloud up --name=chat-room -cloud down --name=chat-room --expunge -dev expunge --name=chat-room -dev run --name=chat-room -serve run --name=chat-room +cloud up --application-name=chat-room +cloud down --application-name=chat-room --expunge +dev expunge --application-name=chat-room +dev run --application-name=chat-room +serve run --application-name=chat-room # Run the application! dev run --application=backend/src/main.py @@ -60,4 +60,4 @@ serve run --tls=external # Run the application! serve run --application=backend/src/main.py -cloud logs --name=chat-room +cloud logs --application-name=chat-room diff --git a/reboot/examples/chat-room/Dockerfile b/reboot/examples/chat-room/Dockerfile index 08538d0e..797a606e 100644 --- a/reboot/examples/chat-room/Dockerfile +++ b/reboot/examples/chat-room/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/reboot-dev/reboot-base:0.46.0 +FROM ghcr.io/reboot-dev/reboot-base:1.0.3 WORKDIR /app diff --git a/reboot/examples/chat-room/README.md b/reboot/examples/chat-room/README.md index b5625244..ece70241 100644 --- a/reboot/examples/chat-room/README.md +++ b/reboot/examples/chat-room/README.md @@ -10,12 +10,12 @@ For the impatient: This repository contains a simple example application written using Reboot. -The [Reboot '.proto' definitions](https://docs.reboot.dev/develop/define/overview/#code-generation) +The [Reboot '.proto' definitions](https://docs.reboot.dev/learn_more/define/overview#code-generation) can be found in the `api/` directory, grouped into subdirectories by proto package, while backend specific code can be found in `backend/` and front end specific code in `web/` and non-React front end in `reboot-non-react-web/`. -_For more information on all of the Reboot examples, please [see the docs](https://docs.reboot.dev/get_started/examples)._ +_For more information on all of the Reboot examples, please [see the docs](https://docs.reboot.dev/full_stack_apps/examples)._ ## Prepare an environment by... diff --git a/reboot/examples/chat-room/pyproject.toml b/reboot/examples/chat-room/pyproject.toml index 142168e1..1c50821b 100644 --- a/reboot/examples/chat-room/pyproject.toml +++ b/reboot/examples/chat-room/pyproject.toml @@ -1,7 +1,7 @@ [project] requires-python = ">= 3.10" dependencies = [ - "reboot==0.46.0", + "reboot==1.0.3", ] [tool.rye] @@ -9,7 +9,7 @@ dev-dependencies = [ "mypy==1.18.1", "pytest>=7.4.2", "types-protobuf>=4.24.0.20240129", - "reboot==0.46.0", + "reboot==1.0.3", ] # This project only uses `rye` to provide `python` and its dependencies, so diff --git a/reboot/examples/chat-room/reboot-non-react-web/package-lock.json b/reboot/examples/chat-room/reboot-non-react-web/package-lock.json index 7c071579..0623da5f 100644 --- a/reboot/examples/chat-room/reboot-non-react-web/package-lock.json +++ b/reboot/examples/chat-room/reboot-non-react-web/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "dependencies": { "@bufbuild/protobuf": "1.10.1", - "@reboot-dev/reboot-web": "0.46.0", + "@reboot-dev/reboot-web": "1.0.3", "@testing-library/jest-dom": "^5.17.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", @@ -71,9 +71,9 @@ } }, "node_modules/@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0", @@ -98,12 +98,12 @@ } }, "node_modules/@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", diff --git a/reboot/examples/chat-room/reboot-non-react-web/package.json b/reboot/examples/chat-room/reboot-non-react-web/package.json index 2694ac85..9720a356 100644 --- a/reboot/examples/chat-room/reboot-non-react-web/package.json +++ b/reboot/examples/chat-room/reboot-non-react-web/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@bufbuild/protobuf": "1.10.1", - "@reboot-dev/reboot-web": "0.46.0", + "@reboot-dev/reboot-web": "1.0.3", "@testing-library/jest-dom": "^5.17.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", diff --git a/reboot/examples/chat-room/requirements-dev.lock b/reboot/examples/chat-room/requirements-dev.lock index 31a9f3ca..8b72ef23 100644 --- a/reboot/examples/chat-room/requirements-dev.lock +++ b/reboot/examples/chat-room/requirements-dev.lock @@ -52,16 +52,21 @@ deprecated==1.2.15 # via opentelemetry-semantic-conventions exceptiongroup==1.2.2 # via anyio + # via pydantic-ai-slim # via pytest fastapi==0.115.12 # via reboot frozenlist==1.4.1 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -90,7 +95,10 @@ hpack==4.1.0 httpcore==1.0.8 # via httpx httpx==0.28.1 + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 @@ -114,9 +122,11 @@ jsonschema-specifications==2025.9.1 # via jsonschema kubernetes-asyncio==31.1.0 # via reboot +logfire-api==4.32.1 + # via pydantic-graph markupsafe==2.1.5 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot multidict==6.1.0 # via aiohttp @@ -132,6 +142,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -178,11 +189,18 @@ pycparser==2.22 # via cffi pydantic==2.12.5 # via fastapi + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot +pydantic-ai-slim==1.87.0 + # via reboot pydantic-core==2.41.5 # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim pydantic-settings==2.13.1 # via mcp pyjwt==2.10.1 @@ -202,7 +220,7 @@ python-ulid==3.1.0 pyyaml==6.0.2 # via kubernetes-asyncio # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications @@ -239,12 +257,15 @@ typing-extensions==4.15.0 # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via typing-inspection # via uvicorn typing-inspection==0.4.2 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot diff --git a/reboot/examples/chat-room/requirements.lock b/reboot/examples/chat-room/requirements.lock index 29a12988..e9cd6645 100644 --- a/reboot/examples/chat-room/requirements.lock +++ b/reboot/examples/chat-room/requirements.lock @@ -52,15 +52,20 @@ deprecated==1.2.15 # via opentelemetry-semantic-conventions exceptiongroup==1.2.2 # via anyio + # via pydantic-ai-slim fastapi==0.115.12 # via reboot frozenlist==1.4.1 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -89,7 +94,10 @@ hpack==4.1.0 httpcore==1.0.8 # via httpx httpx==0.28.1 + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 @@ -111,9 +119,11 @@ jsonschema-specifications==2025.9.1 # via jsonschema kubernetes-asyncio==31.1.0 # via reboot +logfire-api==4.32.1 + # via pydantic-graph markupsafe==2.1.5 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot multidict==6.1.0 # via aiohttp @@ -126,6 +136,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -168,11 +179,18 @@ pycparser==2.22 # via cffi pydantic==2.12.5 # via fastapi + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot +pydantic-ai-slim==1.87.0 + # via reboot pydantic-core==2.41.5 # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim pydantic-settings==2.13.1 # via mcp pyjwt==2.10.1 @@ -191,7 +209,7 @@ python-ulid==3.1.0 pyyaml==6.0.2 # via kubernetes-asyncio # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications @@ -224,12 +242,15 @@ typing-extensions==4.15.0 # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via typing-inspection # via uvicorn typing-inspection==0.4.2 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot diff --git a/reboot/examples/chat-room/web/package-lock.json b/reboot/examples/chat-room/web/package-lock.json index a2325bb4..b10ea958 100644 --- a/reboot/examples/chat-room/web/package-lock.json +++ b/reboot/examples/chat-room/web/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@bufbuild/protobuf": "1.10.1", "@eslint/js": "^9.34.0", - "@reboot-dev/reboot-react": "0.46.0", + "@reboot-dev/reboot-react": "1.0.3", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/eslint__js": "^8.42.3", @@ -995,9 +995,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -1103,15 +1103,18 @@ } }, "node_modules/@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "license": "MIT", "workspaces": [ "examples/*" ], + "engines": { + "node": ">=20" + }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" @@ -1126,9 +1129,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -1166,9 +1169,9 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1223,9 +1226,9 @@ } }, "node_modules/@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0", @@ -1259,15 +1262,15 @@ } }, "node_modules/@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", "license": "Apache-2.0", "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -1293,12 +1296,12 @@ } }, "node_modules/@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -2272,9 +2275,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -2604,9 +2607,9 @@ "license": "MIT" }, "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "license": "MIT", "engines": { "node": ">=18" @@ -3162,9 +3165,9 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -3223,9 +3226,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "license": "MIT", "dependencies": { "ip-address": "10.1.0" @@ -3608,9 +3611,9 @@ } }, "node_modules/hono": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", - "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==", + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -3990,9 +3993,9 @@ "license": "ISC" }, "node_modules/jose": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", - "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -4515,9 +4518,9 @@ } }, "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", "funding": { "type": "opencollective", @@ -4648,9 +4651,9 @@ } }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -5887,12 +5890,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", "peerDependencies": { - "zod": "^3.25 || ^4" + "zod": "^3.25.28 || ^4" } } }, @@ -6373,9 +6376,9 @@ } }, "@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "requires": {} }, "@humanfs/core": { @@ -6438,15 +6441,15 @@ } }, "@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "requires": {} }, "@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "requires": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -6468,9 +6471,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -6509,9 +6512,9 @@ } }, "@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "requires": { "@scarf/scarf": "1.4.0", "typescript": "5.4.5", @@ -6531,14 +6534,14 @@ } }, "@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", - "requires": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", + "requires": { + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -6553,11 +6556,11 @@ } }, "@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "requires": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -7135,9 +7138,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7332,9 +7335,9 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==" }, "content-type": { "version": "1.0.5", @@ -7695,9 +7698,9 @@ } }, "eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==" }, "expect-type": { "version": "1.2.2", @@ -7740,9 +7743,9 @@ } }, "express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "requires": { "ip-address": "10.1.0" } @@ -7973,9 +7976,9 @@ } }, "hono": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", - "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==" + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==" }, "html-encoding-sniffer": { "version": "4.0.0", @@ -8192,9 +8195,9 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "jose": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", - "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==" + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==" }, "js-sha1": { "version": "0.7.0", @@ -8520,9 +8523,9 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==" + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==" }, "pathe": { "version": "2.0.3", @@ -8592,9 +8595,9 @@ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, "qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "requires": { "side-channel": "^1.1.0" } @@ -9284,9 +9287,9 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==" }, "zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "requires": {} } } diff --git a/reboot/examples/chat-room/web/package.json b/reboot/examples/chat-room/web/package.json index 32d59845..0677c32b 100644 --- a/reboot/examples/chat-room/web/package.json +++ b/reboot/examples/chat-room/web/package.json @@ -6,7 +6,7 @@ "dependencies": { "@bufbuild/protobuf": "1.10.1", "@eslint/js": "^9.34.0", - "@reboot-dev/reboot-react": "0.46.0", + "@reboot-dev/reboot-react": "1.0.3", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/eslint__js": "^8.42.3", diff --git a/reboot/examples/chick-potle/.devcontainer/devcontainer.json b/reboot/examples/chick-potle/.devcontainer/devcontainer.json new file mode 100644 index 00000000..88b10cc0 --- /dev/null +++ b/reboot/examples/chick-potle/.devcontainer/devcontainer.json @@ -0,0 +1,35 @@ +// For format details, see https://aka.ms/devcontainer.json. +{ + "name": "Reboot Devcontainer", + "image": "ghcr.io/reboot-dev/reboot-devcontainer", + "remoteUser": "dev", + + "customizations": { + "vscode": { + // The following VS Code extensions are nice to have when developing on + // this Reboot application. + "extensions": [ + // Python support. + "ms-python.python", + // Static type checking for Python. + "ms-python.mypy-type-checker", + // YAPF code formatter for Python. + "eeyore.yapf", + // Automatic import sorting for Python. + "ms-python.isort", + // Protocol buffer support. + "zxh404.vscode-proto3", + // Prettier code formatter. + "esbenp.prettier-vscode" + ] + } + }, + + // Ports from the devcontainer made available on your local machine. + "forwardPorts": [ + // The HTTP port exposed by default in examples. + 3000, + // The gRPC backend port exposed by default in examples. + 9991 + ] +} diff --git a/reboot/examples/chick-potle/.dockerignore b/reboot/examples/chick-potle/.dockerignore new file mode 100644 index 00000000..2d96968d --- /dev/null +++ b/reboot/examples/chick-potle/.dockerignore @@ -0,0 +1,39 @@ +# Python runtime caches and venv — rebuilt inside the image. +.venv/ +__pycache__/ +*.py[cod] +.mypy_cache/ +.pytest_cache/ + +# Reboot runtime state (dev-only). +.rbt/ + +# Generated code — regenerated inside the image by `rbt +# generate`. +backend/api/ +web/api/ + +# Node sources / deps / caches — we only copy `web/dist/` +# into the image. +web/node_modules/ +web/ui/ +web/src/ +web/package.json +web/package-lock.json +web/tsconfig*.json +web/vite.config.ts +web/index.css + +# VCS. +.git/ +.gitignore + +# Editor / OS. +.vscode/ +.idea/ +*.swp +.DS_Store + +# Local-only files. +mcp_servers.json +README.md diff --git a/reboot/examples/chick-potle/.github/workflows/test.yml b/reboot/examples/chick-potle/.github/workflows/test.yml new file mode 100644 index 00000000..007291c8 --- /dev/null +++ b/reboot/examples/chick-potle/.github/workflows/test.yml @@ -0,0 +1,47 @@ +name: Test + +on: + push: + # Run CI (only) on our main branch to show that it is always in a green + # state. Don't run it on other branches; if a developer cares about one + # of the non-main branches they'll run tests manually or open a PR. + branches: + - "main" + pull_request: + # Run CI on pull requests. Passing checks will be required to merge. + branches: + - "**" + merge_group: + # Required if we want to use GitHub's Merge Queue feature. See: + # https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue#triggering-merge-group-checks-with-github-actions + +concurrency: + # Have at most one of these workflows running per branch, cancelling older + # runs that haven't completed yet when they become obsolete. + # + # When pushing new commits to a PR, each one of those commits triggers a new + # workflow run that would normally run to completion, even when subsequent + # pushes to the PR make their result obsolete. This consumes resources for no + # benefit. We override that default behavior to save resources, cancelling any + # older still-running workflows when a new workflow starts + # + # See documentation about the `github` context variables here: + # https://docs.github.com/en/actions/learn-github-actions/contexts#github-context + group: ${{ github.workflow }}-${{ github.ref }} + # Do not cancel runs on the main branch. On the main branch we want every + # commit to be tested. + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + backend-test: + name: Backend test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: pytest (in Dev Container) + uses: devcontainers/ci@v0.3 + with: + runCmd: | + .tests/test.sh diff --git a/reboot/examples/chick-potle/.gitignore b/reboot/examples/chick-potle/.gitignore new file mode 100644 index 00000000..ffb85d3a --- /dev/null +++ b/reboot/examples/chick-potle/.gitignore @@ -0,0 +1,33 @@ +# Python virtual environment. +.venv/ + +# Byte-compiled files. +__pycache__/ +*.py[cod] +*$py.class + +# Generated API code (from `rbt generate`). +backend/api/ +web/api/ + +# Reboot runtime state. +.rbt/ + +# Distribution / packaging. +*.egg-info/ + +# mypy / pytest caches. +.mypy_cache/ +.pytest_cache/ + +# Node / web build artifacts. +web/node_modules/ +web/dist/ + +# IDE / editor. +.idea/ +.vscode/ +*.swp + +# OS. +.DS_Store diff --git a/reboot/examples/chick-potle/.mergequeue/config.yml b/reboot/examples/chick-potle/.mergequeue/config.yml new file mode 100644 index 00000000..6c4cfe2e --- /dev/null +++ b/reboot/examples/chick-potle/.mergequeue/config.yml @@ -0,0 +1,32 @@ +version: 1.0.0 +merge_rules: + publish_status_check: true + labels: + trigger: mergequeue-ready + skip_line: "" + merge_failed: "" + skip_delete_branch: "" + update_latest: true + delete_branch: false + use_rebase: true + enable_comments: true + ci_timeout_mins: 0 + preconditions: + number_of_approvals: 1 + use_github_mergeability: true + conversation_resolution_required: true + merge_mode: + type: default + parallel_mode: null + auto_update: + enabled: false + label: "" + max_runs_for_update: 0 + merge_commit: + use_title_and_body: false + merge_strategy: + name: squash + override_labels: + squash: "" + merge: "" + rebase: mergequeue-rebase diff --git a/reboot/examples/chick-potle/.python-version b/reboot/examples/chick-potle/.python-version new file mode 100644 index 00000000..9919bf8c --- /dev/null +++ b/reboot/examples/chick-potle/.python-version @@ -0,0 +1 @@ +3.10.13 diff --git a/reboot/examples/chick-potle/.rbtrc b/reboot/examples/chick-potle/.rbtrc new file mode 100644 index 00000000..27f3d6e0 --- /dev/null +++ b/reboot/examples/chick-potle/.rbtrc @@ -0,0 +1,49 @@ +# Find API definitions in 'api/'. +generate api/ + +# Tell `rbt generate` where to put generated files. +generate --python=backend/api/ + +# Generate React bindings for web apps (into "web/api/"). +generate --react=web/api +generate --react-extensions + +# Watch if any source files are modified. +dev run --watch=backend/**/*.py + +# Tell `rbt` that this is a Python application. +dev run --python + +# Save state between restarts. +dev run --application-name=chick-potle + +# Run the application! +dev run --application=backend/src/main.py + +# Default to HMR mode when no --config is specified. +dev run --default-config=hmr + +# Hot Module Replacement (HMR): Vite dev server proxied through Envoy. +# Run Vite in a separate terminal: cd web && npm run dev +# Envoy routes "/__/web/**" to Vite for HMR support. +dev run:hmr --mcp-frontend-host=http://localhost:4444 + +# Dist mode: serve pre-built artifacts from "web/dist/" (no Vite HMR). +# Usage: uv run rbt dev run --config=dist +# Requires: cd web && npm run build +dev run:dist --mcp-frontend-host="" + +# When expunging, expunge that state we've saved. +dev expunge --application-name=chick-potle + +# Production (`rbt serve run`) settings, used by the Dockerfile +# when deploying to the Reboot Cloud. In production `web/dist/` +# is served from disk, so no `--mcp-frontend-host` is needed +# (that flag is dev-only anyway). +serve run --python +serve run --application-name=chick-potle +serve run --application=backend/src/main.py + +# Leave TLS termination to the external load balancer; expose +# a non-SSL port to that load balancer. +serve run --tls=external diff --git a/reboot/examples/chick-potle/.tests/test.sh b/reboot/examples/chick-potle/.tests/test.sh new file mode 100755 index 00000000..e6f06401 --- /dev/null +++ b/reboot/examples/chick-potle/.tests/test.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +set -e # Exit if a command exits with an error. +set -x # Echo executed commands to help debug failures. + +# MacOS tests can fail due to a race in `protoc` writing files to +# disk, so now we check only occurrences of the expected lines in +# the output. See https://github.com/reboot-dev/mono/issues/3433 +check_lines_in_file() { + local expected="$1" + local actual="$2" + + while IFS= read -r line; do + if ! grep -Fxq "$line" "$actual"; then + echo "Line $line is missing in the actual output." + exit 1 + fi + done < "$expected" +} + +# Use the published Reboot pip package by default, but allow the +# test system to override it with a different value. +if [ -n "$REBOOT_WHL_FILE" ]; then + # Install the `reboot` package from the specified path + # explicitly, overwriting the version from `pyproject.toml`. + rye remove --no-sync reboot + rye remove --no-sync --dev reboot + rye add --dev reboot --absolute --path="${SANDBOX_ROOT}$REBOOT_WHL_FILE" +fi + +# Force a fresh virtualenv. A pre-existing `.venv/` (e.g., +# carried over from a pre-baked image, or copied between +# containers/host paths during the dev-container test) will +# have absolute shebangs in its console scripts (`rbt`, +# `pytest`) pointing at the path where it was originally +# created, which produces "bad interpreter: No such file or +# directory" when those scripts are executed from a different +# location. `rye sync` only regenerates entry-point scripts +# for packages it reinstalls, so it can't repair an existing +# venv whose shebangs are stale. Nuking and re-syncing here +# guarantees the venv lives at the current path. +rm -rf .venv +rye sync --no-lock + +# Don't `source .venv/bin/activate`: that script bakes in the +# venv's original creation path on its first line, which is +# wrong when the venv has been relocated (e.g., a pre-baked +# image bind-mounted at a different path in CI). The +# console-script wrappers (`rbt`, `pytest`) also have absolute +# shebangs that point at the stale path. Set `PATH` and +# `VIRTUAL_ENV` to the real location ourselves, and invoke the +# tools via `python -m`: the `python` symlink resolves the +# venv from its own location through `pyvenv.cfg`, so it +# survives relocation, and `-m` bypasses the broken script +# shebangs. +VENV_BIN="$(pwd)/.venv/bin" +export PATH="$VENV_BIN:$PATH" +export VIRTUAL_ENV="$(pwd)/.venv" +PYTHON="$VENV_BIN/python" + +# When running in a Bazel test, our `.rbtrc` file ends up in a +# very deep directory structure, which can result in "path too +# long" errors from RocksDB. Explicitly specify a shorter path. +RBT_FLAGS="--state-directory=$(mktemp -d)" + +"$PYTHON" -m reboot.cli.rbt_main $RBT_FLAGS generate + +"$PYTHON" -m pytest backend/ + +if [ -n "$EXPECTED_RBT_DEV_OUTPUT_FILE" ]; then + actual_output_file=$(mktemp) + + # `--config=dist` overrides `.rbtrc`'s default `hmr` config, + # whose `--mcp-frontend-host=http://localhost:4444` would + # have Envoy proxy `/__/web/**` to a Vite dev server. There + # is no Vite running in CI, and on the macOS executable-Envoy + # path that proxy target makes cluster init hang + # indefinitely, so `--terminate-after-health-check` never + # fires. The `dist` config sets `--mcp-frontend-host=""`, + # which skips the proxy entirely. `web/dist/` doesn't need + # to actually exist; the health check only probes gRPC and + # Envoy listeners. + "$PYTHON" -m reboot.cli.rbt_main $RBT_FLAGS dev run \ + --config=dist \ + --terminate-after-health-check \ + > "$actual_output_file" + + check_lines_in_file \ + "${SANDBOX_ROOT}$EXPECTED_RBT_DEV_OUTPUT_FILE" \ + "$actual_output_file" + + rm "$actual_output_file" +fi diff --git a/reboot/examples/chick-potle/BUILD.bazel b/reboot/examples/chick-potle/BUILD.bazel new file mode 100644 index 00000000..3afcc403 --- /dev/null +++ b/reboot/examples/chick-potle/BUILD.bazel @@ -0,0 +1,24 @@ +# This `BUILD.bazel` file allows a `bazel` system (configured outside of +# this repository) to use code in this directory and its subdirectories. + +filegroup( + name = "everything", + srcs = glob( + ["**/*"], + exclude = [ + # Files that may be created by activity in this directory, + # such as manually following the steps of the `README.md` + # file, but which are not part of the "source code" of this + # repository. + ".venv/**/*", + ".rbt/**/*", + ".pytest_cache/**/*", + ".mypy_cache/**/*", + "backend/api/**/*", + "web/api/**/*", + "web/node_modules/**/*", + "web/dist/**/*", + ], + ), + visibility = ["//visibility:public"], +) diff --git a/reboot/examples/chick-potle/Dockerfile b/reboot/examples/chick-potle/Dockerfile new file mode 100644 index 00000000..5e72a685 --- /dev/null +++ b/reboot/examples/chick-potle/Dockerfile @@ -0,0 +1,45 @@ +# Dockerfile for deploying chick-potle to Reboot Cloud. +# +# Prerequisite: run `cd web && npm install && npm run build` +# locally before `docker build` so that `web/dist/` contains the +# bundled UIs. This image copies that prebuilt bundle rather +# than installing Node and rebuilding it here. +FROM ghcr.io/reboot-dev/reboot-base:1.0.3 + +WORKDIR /app + +# Install Python dependencies from the rye-generated lockfile. +# `pip` accepts the `requirements.lock` format directly. This +# layer is cached until the lockfile changes, so app-code edits +# don't trigger a re-install of the dependency tree. The base +# image already includes Reboot itself; the lockfile pins it to +# the matching version. +COPY requirements.lock requirements.txt +RUN pip install -r requirements.txt + +# Copy the API definition and `.rbtrc`, then generate Reboot +# code. Separate layer so regeneration only reruns when the +# API changes. +COPY api/ api/ +COPY .rbtrc .rbtrc +RUN rbt generate + +# Copy the backend source. +COPY backend/src/ backend/src/ + +# Copy the prebuilt web bundle. +COPY web/dist/ web/dist/ + +# Make the Pydantic API definitions in `api/` and the generated +# Reboot bindings in `backend/api/` both importable. They share +# the `ai_chat_food.v1` namespace package, so both directories +# must be on `PYTHONPATH`. `rbt dev run --python` does this +# automatically; `rbt serve run --python` currently only adds +# the generated-code directory, so we set the path here. +ENV PYTHONPATH=/app/api:/app/backend/api + +# Start the Reboot production runtime. `rbt serve run` reads +# its flags (`--python`, `--application=...`, `--application-name=...`, +# `--tls=external`) from `.rbtrc`. `--port` is picked up from +# the `PORT` env var set by Reboot Cloud. +CMD ["rbt", "serve", "run"] diff --git a/reboot/examples/chick-potle/README.md b/reboot/examples/chick-potle/README.md new file mode 100644 index 00000000..9cdd2f88 --- /dev/null +++ b/reboot/examples/chick-potle/README.md @@ -0,0 +1,126 @@ +# Chick-potle + +An AI Chat App built with Reboot that demonstrates a small +food-ordering flow driven from inside an AI chat client. +The AI calls tools to start an order, browse the menu, and +add or remove items; humans see two embedded React UIs — a +menu grid and a cart — rendered alongside the conversation. + +## State model + +- **`User`** — per-user entry point. Auto-constructed for + each authenticated chat user; exposes `start_order`, + which atomically creates a fresh `FoodOrder` (with the + pre-populated menu) and returns its ID. +- **`FoodOrder`** — one ordering session. Holds a `menu` + (the list of items the AI can offer) and a `cart` (the + items the user has added so far, with quantities). The + cart's running total is recomputed from the menu on read, + so item prices stay in sync if the menu is ever updated. + +The user-facing flow is: + +1. The AI calls `User.start_order` and gets back an order + ID. +2. It calls `FoodOrder.show_menu` to open the menu UI in + the chat, where the user can browse items. +3. As the conversation continues, the AI calls + `add_to_cart` / `remove_from_cart` (or the user clicks + "+ Add" buttons in the menu UI), and `show_cart` opens + the cart UI to display the running total. + +`get_menu`, `get_cart`, `add_to_cart`, and `remove_from_cart` +are exposed as MCP tools, so an MCP-aware AI client can +drive the order programmatically. + +## Quick start + +```bash +# Install Python dependencies and create the virtualenv. +rye sync +source .venv/bin/activate + +# Install web dependencies. +cd web && npm install && cd .. + +# Generate API code (Python + React bindings). +rbt generate + +# Build the React UIs. +cd web && npm run build && cd .. +``` + +Then run the app (each command in its own terminal, from the +project directory, with `.venv` activated): + +```bash +# Terminal 1: start the Reboot backend. +rbt dev run + +# Terminal 2: start the Vite dev server for Hot Module Replacement. +cd web && npm run dev +``` + +State persists between restarts under the name `chick-potle` +(configured in `.rbtrc`). To wipe it: + +```bash +rbt dev expunge --application-name=chick-potle +``` + +## Running the tests + +The backend has an in-process test suite that exercises the +`User` and `FoodOrder` servicers via direct Reboot calls — +no MCP client, no browser, no external services. + +```bash +rye sync +source .venv/bin/activate +pytest backend/ +``` + +## Testing with MCPJam Inspector + +`mcp_servers.json` is pre-configured. In another terminal: + +```bash +npx @mcpjam/inspector@v2.4.0 --config mcp_servers.json --server chick-potle +``` + +Try these prompts to exercise each capability: + +1. `Start a new food order.` — exercises `start_order`, + which creates a `FoodOrder` for this user and returns + its ID. +2. `Show me the menu.` — exercises `show_menu` and renders + the menu UI in the chat. +3. `Add a Chicken Burrito and two Mexican Coca-Colas to my + cart.` — exercises `add_to_cart` (the AI looks up the + matching `item_index` from `get_menu`). +4. `What's in my cart?` — exercises `get_cart` and + `show_cart`; the rendered UI lists each line item with + the running total. +5. `Remove the Coca-Colas.` — exercises `remove_from_cart`. + +## Project layout + +``` +chick-potle/ +├── api/ai_chat_food/v1/food.py # State models + method declarations. +├── backend/ +│ ├── api/ # Generated Python bindings. +│ └── src/ +│ ├── main.py # Application entrypoint. +│ └── servicers/food.py # User and FoodOrder servicers. +└── web/ + ├── api/ # Generated React bindings. + └── ui/ + ├── menu/ # Menu grid UI. + └── cart/ # Cart UI. +``` + +## Learn more + +- [Reboot Documentation](https://docs.reboot.dev) +- [MCP Specification](https://modelcontextprotocol.io) diff --git a/reboot/examples/chick-potle/api/ai_chat_food/v1/food.py b/reboot/examples/chick-potle/api/ai_chat_food/v1/food.py new file mode 100644 index 00000000..901c506c --- /dev/null +++ b/reboot/examples/chick-potle/api/ai_chat_food/v1/food.py @@ -0,0 +1,142 @@ +from reboot.api import ( + API, + UI, + Field, + Methods, + Model, + Reader, + Tool, + Transaction, + Type, + Writer, +) + +# -- Helper models. -- + + +class MenuItem(Model): + """A food item on the menu.""" + name: str = Field(tag=1) + description: str = Field(tag=2) + price_cents: int = Field(tag=3) + category: str = Field(tag=4) + emoji: str = Field(tag=5) + + +class CartEntry(Model): + """An item in the cart.""" + item_index: int = Field(tag=1) + quantity: int = Field(tag=2) + + +# -- User models. -- + + +class StartOrderResponse(Model): + order_id: str = Field(tag=1) + + +class UserState(Model): + pass + + +# -- FoodOrder models. -- + + +class FoodOrderState(Model): + menu: list[MenuItem] = Field(tag=1, default_factory=list) + cart: list[CartEntry] = Field(tag=2, default_factory=list) + + +class MenuResponse(Model): + items: list[MenuItem] = Field(tag=1) + + +class CartResponse(Model): + entries: list[CartEntry] = Field(tag=1) + total_cents: int = Field(tag=2) + + +class AddToCartRequest(Model): + """Add a menu item to the cart by index.""" + item_index: int = Field(tag=1) + quantity: int = Field(tag=2) + + +class RemoveFromCartRequest(Model): + """Remove an item from the cart by menu index.""" + item_index: int = Field(tag=1) + + +class CreateOrderRequest(Model): + """Initial menu items.""" + menu: list[MenuItem] = Field(tag=1, default_factory=list) + + +api = API( + User=Type( + state=UserState, + methods=Methods( + start_order=Transaction( + request=None, + response=StartOrderResponse, + description="Start a new food order with a " + "pre-populated menu of items. Returns the " + "order ID. Then show the menu UI.", + mcp=Tool(), + ), + ), + ), + FoodOrder=Type( + state=FoodOrderState, + methods=Methods( + show_menu=UI( + request=None, + path="web/ui/menu", + title="Food Menu", + description="Browse the food menu and add " + "items to the cart.", + ), + show_cart=UI( + request=None, + path="web/ui/cart", + title="Your Cart", + description="View the cart with item details " + "and total price.", + ), + create=Writer( + request=CreateOrderRequest, + response=None, + factory=True, + mcp=None, + ), + get_menu=Reader( + request=None, + response=MenuResponse, + description="Get the food menu.", + mcp=Tool(), + ), + get_cart=Reader( + request=None, + response=CartResponse, + description="Get the current cart contents " + "and total price.", + mcp=Tool(), + ), + add_to_cart=Writer( + request=AddToCartRequest, + response=None, + description="Add a menu item to the cart by " + "its index (0-based). Specify quantity.", + mcp=Tool(), + ), + remove_from_cart=Writer( + request=RemoveFromCartRequest, + response=None, + description="Remove an item from the cart " + "by its menu index.", + mcp=Tool(), + ), + ), + ), +) diff --git a/reboot/examples/chick-potle/backend/.pytest.ini b/reboot/examples/chick-potle/backend/.pytest.ini new file mode 100644 index 00000000..15f5b0c0 --- /dev/null +++ b/reboot/examples/chick-potle/backend/.pytest.ini @@ -0,0 +1,14 @@ +[pytest] +# `ai_chat_food.v1` is a namespace package split across the +# top-level `api/` directory (Pydantic API definitions) and +# `backend/api/` (generated Reboot bindings), so both must be +# on `sys.path`. `src/` carries `main.py` and the `servicers/` +# package. Paths are relative to this file's directory. +pythonpath= + src/ + ../api/ + api/ +# Tests end in `_test.py` to match the convention used by +# sibling Reboot examples (`*_test.py` is one of pytest's +# default discovery patterns alongside `test_*.py`). +python_files=*_test.py diff --git a/reboot/examples/chick-potle/backend/src/main.py b/reboot/examples/chick-potle/backend/src/main.py new file mode 100644 index 00000000..727ddae5 --- /dev/null +++ b/reboot/examples/chick-potle/backend/src/main.py @@ -0,0 +1,24 @@ +import asyncio +import logging +from reboot.aio.applications import Application +from reboot.aio.auth.oauth_providers import Anonymous +from servicers.food import FoodOrderServicer, UserServicer + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", +) + + +async def main() -> None: + application = Application( + servicers=[UserServicer, FoodOrderServicer], + # `User` is an auto-constructed state type, so Reboot + # needs an OAuth provider to identify the caller. + oauth=Anonymous(), + ) + await application.run() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/reboot/examples/chick-potle/backend/src/servicers/food.py b/reboot/examples/chick-potle/backend/src/servicers/food.py new file mode 100644 index 00000000..ef690aed --- /dev/null +++ b/reboot/examples/chick-potle/backend/src/servicers/food.py @@ -0,0 +1,163 @@ +from ai_chat_food.v1.food import CartEntry, MenuItem +from ai_chat_food.v1.food_rbt import FoodOrder, User +from reboot.aio.contexts import ( + ReaderContext, + TransactionContext, + WriterContext, +) + +MENU_ITEMS = [ + MenuItem( + name="Chicken Burrito", + description="Flour tortilla, cilantro-lime rice, black beans, " + "chicken, fresh tomato salsa, sour cream, cheese", + price_cents=1115, + category="Burritos", + emoji="🌯", + ), + MenuItem( + name="Steak Burrito", + description="Flour tortilla, cilantro-lime rice, pinto beans, " + "steak, roasted chili-corn salsa, guacamole", + price_cents=1240, + category="Burritos", + emoji="🌯", + ), + MenuItem( + name="Chicken Bowl", + description="Cilantro-lime rice, black beans, chicken, fajita " + "veggies, fresh tomato salsa, cheese, lettuce", + price_cents=1115, + category="Bowls", + emoji="🥘", + ), + MenuItem( + name="Barbacoa Bowl", + description="Cilantro-lime rice, pinto beans, barbacoa, sour cream, " + "cheese, roasted chili-corn salsa", + price_cents=1240, + category="Bowls", + emoji="🥘", + ), + MenuItem( + name="Chicken Tacos", + description="Three crispy corn tortillas, chicken, fresh tomato " + "salsa, cheese, lettuce", + price_cents=1115, + category="Tacos", + emoji="🌮", + ), + MenuItem( + name="Carnitas Tacos", + description="Three soft flour tortillas, carnitas, tomatillo-green " + "chili salsa, sour cream, cheese", + price_cents=1115, + category="Tacos", + emoji="🌮", + ), + MenuItem( + name="Chips & Guacamole", + description="Fresh tortilla chips with hand-mashed avocado, lime, " + "cilantro, jalapeno", + price_cents=595, + category="Sides", + emoji="🥑", + ), + MenuItem( + name="Chips & Queso Blanco", + description="Fresh tortilla chips with creamy white queso dip", + price_cents=545, + category="Sides", + emoji="🧀", + ), + MenuItem( + name="Mexican Coca-Cola", + description="Classic Coca-Cola made with real cane sugar in a " + "glass bottle", + price_cents=350, + category="Drinks", + emoji="🥤", + ), + MenuItem( + name="Lemonade", + description="Tractor Organic fresh-squeezed lemonade", + price_cents=325, + category="Drinks", + emoji="🍋", + ), +] + + +class UserServicer(User.Servicer): + + async def start_order( + self, + context: TransactionContext, + ) -> User.StartOrderResponse: + order, _ = await FoodOrder.create( + context, + menu=MENU_ITEMS, + ) + return User.StartOrderResponse( + order_id=order.state_id, + ) + + +class FoodOrderServicer(FoodOrder.Servicer): + + async def create( + self, + context: WriterContext, + request: FoodOrder.CreateRequest, + ) -> None: + self.state.menu = list(request.menu) + self.state.cart = [] + + async def get_menu( + self, + context: ReaderContext, + ) -> FoodOrder.GetMenuResponse: + return FoodOrder.GetMenuResponse(items=self.state.menu) + + async def get_cart( + self, + context: ReaderContext, + ) -> FoodOrder.GetCartResponse: + total = 0 + for entry in self.state.cart: + if 0 <= entry.item_index < len(self.state.menu): + total += ( + self.state.menu[entry.item_index].price_cents * + entry.quantity + ) + return FoodOrder.GetCartResponse( + entries=self.state.cart, + total_cents=total, + ) + + async def add_to_cart( + self, + context: WriterContext, + request: FoodOrder.AddToCartRequest, + ) -> None: + if request.item_index < 0 or request.item_index >= len( + self.state.menu + ): + raise ValueError("Invalid item index.") + quantity = request.quantity if request.quantity > 0 else 1 + for entry in self.state.cart: + if entry.item_index == request.item_index: + entry.quantity += quantity + return + self.state.cart.append( + CartEntry(item_index=request.item_index, quantity=quantity) + ) + + async def remove_from_cart( + self, + context: WriterContext, + request: FoodOrder.RemoveFromCartRequest, + ) -> None: + self.state.cart = [ + e for e in self.state.cart if e.item_index != request.item_index + ] diff --git a/reboot/examples/chick-potle/backend/tests/food_test.py b/reboot/examples/chick-potle/backend/tests/food_test.py new file mode 100644 index 00000000..024acc03 --- /dev/null +++ b/reboot/examples/chick-potle/backend/tests/food_test.py @@ -0,0 +1,154 @@ +"""Tests for the chick-potle backend. + +Covers `User.start_order` and `FoodOrder` CRUD via direct +Reboot calls.""" +import unittest +from ai_chat_food.v1.food_rbt import FoodOrder, User +from reboot.aio.applications import Application +from reboot.aio.auth.authorizers import allow +from reboot.aio.tests import Reboot +from servicers.food import FoodOrderServicer, UserServicer + +# Production servicers intentionally don't define an +# `authorizer()`: in development Reboot defaults to allow-all, +# but in production an absent authorizer denies by default, +# which we rely on so that no permissive code accidentally +# ships. The tests run against the production-mode harness, so +# we extend each servicer here and grant `allow()` for the +# duration of the suite. + + +class PermissiveUserServicer(UserServicer): + + def authorizer(self): + return allow() + + +class PermissiveFoodOrderServicer(FoodOrderServicer): + + def authorizer(self): + return allow() + + +APPLICATION_SERVICERS = [ + PermissiveUserServicer, + PermissiveFoodOrderServicer, +] + + +class ServicerTest(unittest.IsolatedAsyncioTestCase): + """Unit tests for `User` and `FoodOrder` servicers.""" + + async def asyncSetUp(self) -> None: + self.rbt = Reboot() + await self.rbt.start() + await self.rbt.up( + Application(servicers=APPLICATION_SERVICERS), + ) + self.user_id = "alice" + self.context = self.rbt.create_external_context( + name=f"test-{self.id()}", + bearer_token=self.rbt.make_valid_oauth_access_token( + user_id=self.user_id, + ), + ) + # `User` is an auto-constructed state type: in + # production the MCP session's "new session" hook + # calls `_auto_construct` for the authenticated user. + # Tests don't go through that hook, so we do it here. + await PermissiveUserServicer._auto_construct( + self.context, + state_id=self.user_id, + ) + + async def asyncTearDown(self) -> None: + await self.rbt.stop() + + async def test_start_order_returns_food_order_id(self) -> None: + """`User.start_order` creates a `FoodOrder` whose + menu is pre-populated and whose cart is empty.""" + user = User.ref(self.user_id) + response = await user.start_order(self.context) + self.assertTrue(response.order_id) + + order = FoodOrder.ref(response.order_id) + menu_response = await order.get_menu(self.context) + # The menu is constant, populated by the servicer; we + # just check that it isn't empty and that the items + # have the fields the UI expects. + self.assertGreater(len(menu_response.items), 0) + first = menu_response.items[0] + self.assertTrue(first.name) + self.assertTrue(first.category) + self.assertGreater(first.price_cents, 0) + + cart_response = await order.get_cart(self.context) + self.assertEqual(cart_response.entries, []) + self.assertEqual(cart_response.total_cents, 0) + + async def test_add_and_remove_from_cart(self) -> None: + """Adding the same item twice increments its + quantity instead of creating a duplicate row, and + the cart total reflects menu prices.""" + user = User.ref(self.user_id) + start_response = await user.start_order(self.context) + order = FoodOrder.ref(start_response.order_id) + + menu_response = await order.get_menu(self.context) + first_price = menu_response.items[0].price_cents + second_price = menu_response.items[1].price_cents + + # Add two of item 0 and one of item 1; the second + # add for item 0 should bump quantity, not append. + await order.add_to_cart(self.context, item_index=0, quantity=1) + await order.add_to_cart(self.context, item_index=0, quantity=1) + await order.add_to_cart(self.context, item_index=1, quantity=1) + + cart_response = await order.get_cart(self.context) + self.assertEqual(len(cart_response.entries), 2) + by_index = { + entry.item_index: entry.quantity for entry in cart_response.entries + } + self.assertEqual(by_index[0], 2) + self.assertEqual(by_index[1], 1) + self.assertEqual( + cart_response.total_cents, + 2 * first_price + second_price, + ) + + # Remove item 0 entirely; item 1 should remain. + await order.remove_from_cart(self.context, item_index=0) + cart_response = await order.get_cart(self.context) + self.assertEqual(len(cart_response.entries), 1) + self.assertEqual(cart_response.entries[0].item_index, 1) + self.assertEqual(cart_response.total_cents, second_price) + + async def test_add_to_cart_default_quantity(self) -> None: + """A `quantity` of 0 (the protobuf default) is + treated as 1, matching the AI-friendly contract in + `food.py`.""" + user = User.ref(self.user_id) + start_response = await user.start_order(self.context) + order = FoodOrder.ref(start_response.order_id) + + await order.add_to_cart(self.context, item_index=0, quantity=0) + cart_response = await order.get_cart(self.context) + self.assertEqual(len(cart_response.entries), 1) + self.assertEqual(cart_response.entries[0].quantity, 1) + + async def test_add_to_cart_invalid_index_raises(self) -> None: + """Out-of-range indexes raise `ValueError` rather + than silently corrupting the cart.""" + user = User.ref(self.user_id) + start_response = await user.start_order(self.context) + order = FoodOrder.ref(start_response.order_id) + + menu_response = await order.get_menu(self.context) + too_large = len(menu_response.items) + + with self.assertRaises(Exception): + await order.add_to_cart( + self.context, item_index=too_large, quantity=1 + ) + with self.assertRaises(Exception): + await order.add_to_cart(self.context, item_index=-1, quantity=1) diff --git a/reboot/examples/chick-potle/mcp_servers.json b/reboot/examples/chick-potle/mcp_servers.json new file mode 100644 index 00000000..6b2fa362 --- /dev/null +++ b/reboot/examples/chick-potle/mcp_servers.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "chick-potle": { + "url": "http://localhost:9991/mcp", + "useOAuth": true + } + } +} diff --git a/reboot/examples/chick-potle/pyproject.toml b/reboot/examples/chick-potle/pyproject.toml new file mode 100644 index 00000000..75dc941d --- /dev/null +++ b/reboot/examples/chick-potle/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "chick-potle" +version = "0.1.0" +requires-python = ">= 3.10" +dependencies = [ + "httpx>=0.27,<1.0", + "uuid7>=0.1.0", + "anyio>=4.0.0", + "reboot==1.0.3", +] + +[tool.rye] +dev-dependencies = [ + "pytest>=7.4", + "reboot==1.0.3", +] + +# This project only uses `rye` to provide `python` and its dependencies. +virtual = true +managed = true diff --git a/reboot/examples/chick-potle/requirements-dev.lock b/reboot/examples/chick-potle/requirements-dev.lock new file mode 100644 index 00000000..a6429a22 --- /dev/null +++ b/reboot/examples/chick-potle/requirements-dev.lock @@ -0,0 +1,287 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false + +aiofiles==23.2.1 + # via reboot +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.2 + # via kubernetes-asyncio + # via reboot +aiosignal==1.4.0 + # via aiohttp +annotated-types==0.7.0 + # via pydantic +anyio==4.13.0 + # via httpx + # via mcp + # via reboot + # via sse-starlette + # via starlette +async-timeout==5.0.1 + # via aiohttp +attrs==26.1.0 + # via aiohttp + # via jsonschema + # via referencing +bitarray==3.8.0 + # via reboot +certifi==2026.4.22 + # via httpcore + # via httpx + # via kubernetes-asyncio +cffi==1.17.1 + # via cryptography + # via reboot +click==8.3.3 + # via uvicorn +colorama==0.4.6 + # via reboot +cryptography==44.0.0 + # via pyjwt + # via reboot +deprecated==1.3.1 + # via opentelemetry-api + # via opentelemetry-exporter-otlp-proto-grpc + # via opentelemetry-semantic-conventions +exceptiongroup==1.3.1 + # via anyio + # via pydantic-ai-slim + # via pytest +fastapi==0.115.12 + # via reboot +frozenlist==1.8.0 + # via aiohttp + # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim +googleapis-common-protos==1.65.0 + # via grpcio-status + # via opentelemetry-exporter-otlp-proto-grpc + # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim +grpc-interceptor==0.15.4 + # via reboot +grpcio==1.64.3 + # via grpc-interceptor + # via grpcio-health-checking + # via grpcio-reflection + # via grpcio-status + # via grpcio-tools + # via opentelemetry-exporter-otlp-proto-grpc + # via reboot +grpcio-health-checking==1.64.3 + # via reboot +grpcio-reflection==1.64.3 + # via reboot +grpcio-status==1.64.3 + # via reboot +grpcio-tools==1.64.3 + # via reboot +h11==0.16.0 + # via httpcore + # via uvicorn +h2==4.3.0 + # via reboot +hpack==4.1.0 + # via h2 +httpcore==1.0.9 + # via httpx +httpx==0.28.1 + # via genai-prices + # via mcp + # via pydantic-ai-slim + # via pydantic-graph +httpx-sse==0.4.3 + # via mcp +hyperframe==6.1.0 + # via h2 +idna==3.13 + # via anyio + # via httpx + # via yarl +importlib-metadata==8.5.0 + # via opentelemetry-api +iniconfig==2.3.0 + # via pytest +jinja2==3.1.2 + # via jinja2-strcase + # via reboot +jinja2-strcase==0.0.2 + # via reboot +jsonschema==4.26.0 + # via mcp +jsonschema-specifications==2025.9.1 + # via jsonschema +kubernetes-asyncio==31.1.0 + # via reboot +logfire-api==4.32.1 + # via pydantic-graph +markupsafe==3.0.3 + # via jinja2 +mcp==1.27.0 + # via reboot +multidict==6.7.1 + # via aiohttp + # via yarl +mypy-protobuf==3.6.0 + # via reboot +opentelemetry-api==1.28.1 + # via opentelemetry-exporter-otlp-proto-grpc + # via opentelemetry-instrumentation + # via opentelemetry-instrumentation-grpc + # via opentelemetry-sdk + # via opentelemetry-semantic-conventions + # via pydantic-ai-slim + # via reboot +opentelemetry-exporter-otlp-proto-common==1.28.1 + # via opentelemetry-exporter-otlp-proto-grpc +opentelemetry-exporter-otlp-proto-grpc==1.28.1 + # via reboot +opentelemetry-instrumentation==0.49b1 + # via opentelemetry-instrumentation-grpc +opentelemetry-instrumentation-grpc==0.49b1 + # via reboot +opentelemetry-proto==1.28.1 + # via opentelemetry-exporter-otlp-proto-common + # via opentelemetry-exporter-otlp-proto-grpc +opentelemetry-sdk==1.28.1 + # via opentelemetry-exporter-otlp-proto-grpc + # via reboot +opentelemetry-semantic-conventions==0.49b1 + # via opentelemetry-instrumentation + # via opentelemetry-instrumentation-grpc + # via opentelemetry-sdk +packaging==26.2 + # via opentelemetry-instrumentation + # via pytest + # via reboot +pathspec==0.12.1 + # via reboot +pluggy==1.6.0 + # via pytest +propcache==0.4.1 + # via aiohttp + # via yarl +protobuf==5.28.3 + # via googleapis-common-protos + # via grpcio-health-checking + # via grpcio-reflection + # via grpcio-status + # via grpcio-tools + # via mypy-protobuf + # via opentelemetry-proto + # via reboot +psutil==6.0.0 + # via reboot +pycparser==3.0 + # via cffi +pydantic==2.13.3 + # via fastapi + # via genai-prices + # via mcp + # via pydantic-ai-slim + # via pydantic-graph + # via pydantic-settings + # via reboot +pydantic-ai-slim==1.87.0 + # via reboot +pydantic-core==2.46.3 + # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim +pydantic-settings==2.14.0 + # via mcp +pygments==2.20.0 + # via pytest +pyjwt==2.10.1 + # via mcp + # via reboot +pyprctl==0.1.3 + # via reboot +pytest==9.0.3 +python-dateutil==2.9.0.post0 + # via kubernetes-asyncio +python-dotenv==1.2.2 + # via pydantic-settings +python-multipart==0.0.27 + # via mcp +python-ulid==3.1.0 + # via reboot +pyyaml==6.0.2 + # via kubernetes-asyncio + # via reboot +reboot==1.0.3 +referencing==0.37.0 + # via jsonschema + # via jsonschema-specifications +rpds-py==0.30.0 + # via jsonschema + # via referencing +setuptools==82.0.1 + # via grpcio-tools +six==1.17.0 + # via kubernetes-asyncio + # via python-dateutil +sse-starlette==3.0.3 + # via mcp +starlette==0.46.2 + # via fastapi + # via mcp + # via reboot +tabulate==0.9.0 + # via reboot +tomli==2.4.1 + # via pytest +types-protobuf==7.34.1.20260408 + # via mypy-protobuf +typing-extensions==4.15.0 + # via aiosignal + # via anyio + # via exceptiongroup + # via fastapi + # via mcp + # via multidict + # via opentelemetry-sdk + # via pydantic + # via pydantic-core + # via reboot + # via referencing + # via typing-inspection + # via uvicorn +typing-inspection==0.4.2 + # via mcp + # via pydantic + # via pydantic-ai-slim + # via pydantic-graph + # via pydantic-settings +tzlocal==5.3 + # via reboot +urllib3==1.26.15 + # via kubernetes-asyncio + # via reboot +uuid7==0.1.0 +uuid7-standard==1.1.0 + # via reboot +uvicorn==0.34.0 + # via mcp + # via reboot +watchdog==6.0.0 + # via reboot +websockets==15.0.1 + # via reboot +wrapt==1.17.3 + # via deprecated + # via opentelemetry-instrumentation + # via opentelemetry-instrumentation-grpc +yarl==1.23.0 + # via aiohttp +zipp==3.23.1 + # via importlib-metadata diff --git a/reboot/examples/chick-potle/requirements.lock b/reboot/examples/chick-potle/requirements.lock new file mode 100644 index 00000000..569ae234 --- /dev/null +++ b/reboot/examples/chick-potle/requirements.lock @@ -0,0 +1,276 @@ +# generated by rye +# use `rye lock` or `rye sync` to update this lockfile +# +# last locked with the following flags: +# pre: false +# features: [] +# all-features: false +# with-sources: false + +aiofiles==23.2.1 + # via reboot +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.2 + # via kubernetes-asyncio + # via reboot +aiosignal==1.4.0 + # via aiohttp +annotated-types==0.7.0 + # via pydantic +anyio==4.13.0 + # via httpx + # via mcp + # via reboot + # via sse-starlette + # via starlette +async-timeout==5.0.1 + # via aiohttp +attrs==26.1.0 + # via aiohttp + # via jsonschema + # via referencing +bitarray==3.8.0 + # via reboot +certifi==2026.4.22 + # via httpcore + # via httpx + # via kubernetes-asyncio +cffi==1.17.1 + # via cryptography + # via reboot +click==8.3.3 + # via uvicorn +colorama==0.4.6 + # via reboot +cryptography==44.0.0 + # via pyjwt + # via reboot +deprecated==1.3.1 + # via opentelemetry-api + # via opentelemetry-exporter-otlp-proto-grpc + # via opentelemetry-semantic-conventions +exceptiongroup==1.3.1 + # via anyio + # via pydantic-ai-slim +fastapi==0.115.12 + # via reboot +frozenlist==1.8.0 + # via aiohttp + # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim +googleapis-common-protos==1.65.0 + # via grpcio-status + # via opentelemetry-exporter-otlp-proto-grpc + # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim +grpc-interceptor==0.15.4 + # via reboot +grpcio==1.64.3 + # via grpc-interceptor + # via grpcio-health-checking + # via grpcio-reflection + # via grpcio-status + # via grpcio-tools + # via opentelemetry-exporter-otlp-proto-grpc + # via reboot +grpcio-health-checking==1.64.3 + # via reboot +grpcio-reflection==1.64.3 + # via reboot +grpcio-status==1.64.3 + # via reboot +grpcio-tools==1.64.3 + # via reboot +h11==0.16.0 + # via httpcore + # via uvicorn +h2==4.3.0 + # via reboot +hpack==4.1.0 + # via h2 +httpcore==1.0.9 + # via httpx +httpx==0.28.1 + # via genai-prices + # via mcp + # via pydantic-ai-slim + # via pydantic-graph +httpx-sse==0.4.3 + # via mcp +hyperframe==6.1.0 + # via h2 +idna==3.13 + # via anyio + # via httpx + # via yarl +importlib-metadata==8.5.0 + # via opentelemetry-api +jinja2==3.1.2 + # via jinja2-strcase + # via reboot +jinja2-strcase==0.0.2 + # via reboot +jsonschema==4.26.0 + # via mcp +jsonschema-specifications==2025.9.1 + # via jsonschema +kubernetes-asyncio==31.1.0 + # via reboot +logfire-api==4.32.1 + # via pydantic-graph +markupsafe==3.0.3 + # via jinja2 +mcp==1.27.0 + # via reboot +multidict==6.7.1 + # via aiohttp + # via yarl +mypy-protobuf==3.6.0 + # via reboot +opentelemetry-api==1.28.1 + # via opentelemetry-exporter-otlp-proto-grpc + # via opentelemetry-instrumentation + # via opentelemetry-instrumentation-grpc + # via opentelemetry-sdk + # via opentelemetry-semantic-conventions + # via pydantic-ai-slim + # via reboot +opentelemetry-exporter-otlp-proto-common==1.28.1 + # via opentelemetry-exporter-otlp-proto-grpc +opentelemetry-exporter-otlp-proto-grpc==1.28.1 + # via reboot +opentelemetry-instrumentation==0.49b1 + # via opentelemetry-instrumentation-grpc +opentelemetry-instrumentation-grpc==0.49b1 + # via reboot +opentelemetry-proto==1.28.1 + # via opentelemetry-exporter-otlp-proto-common + # via opentelemetry-exporter-otlp-proto-grpc +opentelemetry-sdk==1.28.1 + # via opentelemetry-exporter-otlp-proto-grpc + # via reboot +opentelemetry-semantic-conventions==0.49b1 + # via opentelemetry-instrumentation + # via opentelemetry-instrumentation-grpc + # via opentelemetry-sdk +packaging==26.2 + # via opentelemetry-instrumentation + # via reboot +pathspec==0.12.1 + # via reboot +propcache==0.4.1 + # via aiohttp + # via yarl +protobuf==5.28.3 + # via googleapis-common-protos + # via grpcio-health-checking + # via grpcio-reflection + # via grpcio-status + # via grpcio-tools + # via mypy-protobuf + # via opentelemetry-proto + # via reboot +psutil==6.0.0 + # via reboot +pycparser==3.0 + # via cffi +pydantic==2.13.3 + # via fastapi + # via genai-prices + # via mcp + # via pydantic-ai-slim + # via pydantic-graph + # via pydantic-settings + # via reboot +pydantic-ai-slim==1.87.0 + # via reboot +pydantic-core==2.46.3 + # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim +pydantic-settings==2.14.0 + # via mcp +pyjwt==2.10.1 + # via mcp + # via reboot +pyprctl==0.1.3 + # via reboot +python-dateutil==2.9.0.post0 + # via kubernetes-asyncio +python-dotenv==1.2.2 + # via pydantic-settings +python-multipart==0.0.27 + # via mcp +python-ulid==3.1.0 + # via reboot +pyyaml==6.0.2 + # via kubernetes-asyncio + # via reboot +reboot==1.0.3 +referencing==0.37.0 + # via jsonschema + # via jsonschema-specifications +rpds-py==0.30.0 + # via jsonschema + # via referencing +setuptools==82.0.1 + # via grpcio-tools +six==1.17.0 + # via kubernetes-asyncio + # via python-dateutil +sse-starlette==3.0.3 + # via mcp +starlette==0.46.2 + # via fastapi + # via mcp + # via reboot +tabulate==0.9.0 + # via reboot +types-protobuf==7.34.1.20260408 + # via mypy-protobuf +typing-extensions==4.15.0 + # via aiosignal + # via anyio + # via exceptiongroup + # via fastapi + # via mcp + # via multidict + # via opentelemetry-sdk + # via pydantic + # via pydantic-core + # via reboot + # via referencing + # via typing-inspection + # via uvicorn +typing-inspection==0.4.2 + # via mcp + # via pydantic + # via pydantic-ai-slim + # via pydantic-graph + # via pydantic-settings +tzlocal==5.3 + # via reboot +urllib3==1.26.15 + # via kubernetes-asyncio + # via reboot +uuid7==0.1.0 +uuid7-standard==1.1.0 + # via reboot +uvicorn==0.34.0 + # via mcp + # via reboot +watchdog==6.0.0 + # via reboot +websockets==15.0.1 + # via reboot +wrapt==1.17.3 + # via deprecated + # via opentelemetry-instrumentation + # via opentelemetry-instrumentation-grpc +yarl==1.23.0 + # via aiohttp +zipp==3.23.1 + # via importlib-metadata diff --git a/reboot/examples/chick-potle/web/index.css b/reboot/examples/chick-potle/web/index.css new file mode 100644 index 00000000..66299ece --- /dev/null +++ b/reboot/examples/chick-potle/web/index.css @@ -0,0 +1,49 @@ +:root { + /* Chick-fil-A brand palette */ + --color-bg: #ffffff; + --color-bg-dark: #f4f4f4; + --color-border: #e0e0e0; + --color-text: #2c2c2c; + --color-text-muted: #6b6b6b; + --color-green: #4ade80; + --color-blue: #60a5fa; + --color-yellow: #d4a853; + --color-pink: #f472b6; + --color-purple: #a78bfa; + --color-orange: #fb923c; + --cfa-red: #e51636; + --cfa-red-dark: #c8102e; + --cfa-white: #ffffff; + --cfa-dark: #2c2c2c; + --cfa-light-gray: #f4f4f4; + --cfa-mid-gray: #e0e0e0; + --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, + monospace; + --font-brand: "Lato", "Helvetica Neue", Arial, sans-serif; +} + +[data-theme="light"] { + --color-bg: #ffffff; + --color-bg-dark: #f4f4f4; + --color-border: #e0e0e0; + --color-text: #2c2c2c; + --color-text-muted: #6b6b6b; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html, +body, +#root { + width: 100%; +} + +body { + font-family: var(--font-brand); + background: var(--color-bg); + color: var(--color-text); +} diff --git a/reboot/examples/chick-potle/web/package-lock.json b/reboot/examples/chick-potle/web/package-lock.json new file mode 100644 index 00000000..a9e4ef77 --- /dev/null +++ b/reboot/examples/chick-potle/web/package-lock.json @@ -0,0 +1,3517 @@ +{ + "name": "chick-potle-web", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "chick-potle-web", + "version": "0.1.0", + "dependencies": { + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "zod": "^3.25.0" + }, + "devDependencies": { + "@types/react": "^18.2.67", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.7.0", + "concurrently": "^9.1.2", + "typescript": "^5.9.2", + "vite": "^6.3.5", + "vite-plugin-singlefile": "^2.0.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.1.tgz", + "integrity": "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==", + "license": "(Apache-2.0 AND BSD-3-Clause)", + "peer": true + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@modelcontextprotocol/ext-apps": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", + "license": "MIT", + "workspaces": [ + "examples/*" + ], + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@reboot-dev/reboot-api": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "1.4.0", + "typescript": "5.4.5", + "zod": "^3.25.51" + }, + "peerDependencies": { + "@bufbuild/protobuf": "1.10.1" + } + }, + "node_modules/@reboot-dev/reboot-api/node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@reboot-dev/reboot-react": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", + "license": "Apache-2.0", + "dependencies": { + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", + "@scarf/scarf": "1.4.0", + "@types/uuid": "^9.0.4", + "js-sha1": "0.7.0", + "tslib": "^2.6.2", + "typescript": "4.8.4", + "uuid": "11.1.0" + }, + "peerDependencies": { + "@bufbuild/protobuf": "1.10.1", + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@reboot-dev/reboot-react/node_modules/typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/@reboot-dev/reboot-web": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", + "license": "Apache-2.0", + "dependencies": { + "@reboot-dev/reboot-api": "1.0.3", + "@scarf/scarf": "1.4.0", + "js-sha1": "0.7.0", + "lru-cache-idb": "^0.5.2", + "tslib": "^2.6.2", + "typescript": "4.8.4", + "uuid": "11.1.0" + }, + "peerDependencies": { + "@bufbuild/protobuf": "1.10.1" + } + }, + "node_modules/@reboot-dev/reboot-web/node_modules/typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", + "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-sha1": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/js-sha1/-/js-sha1-0.7.0.tgz", + "integrity": "sha512-oQZ1Mo7440BfLSv9TX87VNEyU52pXPVG19F9PL3gTgNt0tVxlZ8F4O6yze3CLuLx28TxotxvlyepCNaaV0ZjMw==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lru-cache-idb": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/lru-cache-idb/-/lru-cache-idb-0.5.2.tgz", + "integrity": "sha512-POlgTgPauh/q/mdh51G828HXqSI+/7KU6tKf59lppkTeTUznztGcqjeUxwjJPVOpKTgWH28DOFtbp9DpXq0zLA==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-singlefile": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/vite-plugin-singlefile/-/vite-plugin-singlefile-2.3.3.tgz", + "integrity": "sha512-XVnGH0QzbOa8fxRSsHdCarVN1BSBXNi7uLMQYlrGRN5apdHkk62XQWRJhVever0lnfuyBkwn+kvVChdm/OoOUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">18.0.0" + }, + "peerDependencies": { + "rollup": "^4.59.0", + "vite": "^5.4.21 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/reboot/examples/chick-potle/web/package.json b/reboot/examples/chick-potle/web/package.json new file mode 100644 index 00000000..fc1acab1 --- /dev/null +++ b/reboot/examples/chick-potle/web/package.json @@ -0,0 +1,33 @@ +{ + "name": "chick-potle-web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build:menu": "vite build --mode menu", + "build:watch:menu": "vite build --mode menu --watch", + "build:cart": "vite build --mode cart", + "build:watch:cart": "vite build --mode cart --watch", + "build": "tsc --noEmit && npm run build:menu && npm run build:cart", + "build:watch": "concurrently \"npm:build:watch:*\"" + }, + "dependencies": { + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-react": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "zod": "^3.25.0" + }, + "devDependencies": { + "@types/react": "^18.2.67", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.7.0", + "concurrently": "^9.1.2", + "typescript": "^5.9.2", + "vite": "^6.3.5", + "vite-plugin-singlefile": "^2.0.3" + } +} diff --git a/reboot/examples/chick-potle/web/tsconfig.app.json b/reboot/examples/chick-potle/web/tsconfig.app.json new file mode 100644 index 00000000..910e9002 --- /dev/null +++ b/reboot/examples/chick-potle/web/tsconfig.app.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@api/*": ["./api/*"] + } + }, + "include": ["ui"] +} diff --git a/reboot/examples/chick-potle/web/tsconfig.json b/reboot/examples/chick-potle/web/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/reboot/examples/chick-potle/web/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/reboot/examples/chick-potle/web/tsconfig.node.json b/reboot/examples/chick-potle/web/tsconfig.node.json new file mode 100644 index 00000000..4d19ae8f --- /dev/null +++ b/reboot/examples/chick-potle/web/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/reboot/examples/chick-potle/web/ui/cart/App.module.css b/reboot/examples/chick-potle/web/ui/cart/App.module.css new file mode 100644 index 00000000..5beb6e00 --- /dev/null +++ b/reboot/examples/chick-potle/web/ui/cart/App.module.css @@ -0,0 +1,132 @@ +.container { + background: var(--cfa-white); + color: var(--cfa-dark); + font-family: var(--font-brand); + padding: 20px; +} + +.header { + font-size: 22px; + font-weight: 900; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--cfa-red); + margin-bottom: 16px; + text-align: center; +} + +.empty { + text-align: center; + color: var(--color-text-muted); + font-size: 13px; + padding: 24px; +} + +.list { + display: flex; + flex-direction: column; + gap: 8px; +} + +.row { + display: flex; + align-items: center; + gap: 12px; + background: var(--cfa-light-gray); + border: 1px solid var(--cfa-mid-gray); + border-radius: 6px; + padding: 10px 12px; +} + +.emoji { + font-size: 28px; + flex-shrink: 0; +} + +.details { + flex: 1; + min-width: 0; +} + +.name { + font-size: 13px; + font-weight: 800; + color: var(--cfa-dark); +} + +.meta { + font-size: 10px; + color: var(--color-text-muted); + margin-top: 2px; +} + +.linePrice { + font-size: 14px; + font-weight: 800; + color: var(--cfa-dark); + flex-shrink: 0; +} + +.removeButton { + width: 28px; + height: 28px; + font-size: 14px; + font-family: var(--font-brand); + font-weight: bold; + background: var(--cfa-red); + color: var(--cfa-white); + border: none; + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s ease; + flex-shrink: 0; +} + +.removeButton:hover:not(:disabled) { + background: var(--cfa-red-dark); +} + +.removeButton:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +.totalRow { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 16px; + padding-top: 12px; + border-top: 2px solid var(--cfa-red); +} + +.totalLabel { + font-size: 16px; + font-weight: 900; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--cfa-dark); +} + +.totalPrice { + font-size: 22px; + font-weight: 900; + color: var(--cfa-red); +} + +.itemCount { + text-align: right; + font-size: 11px; + color: var(--color-text-muted); + margin-top: 4px; +} + +.loading { + color: var(--color-text-muted); + font-size: 12px; + text-align: center; + padding: 24px; +} diff --git a/reboot/examples/chick-potle/web/ui/cart/App.tsx b/reboot/examples/chick-potle/web/ui/cart/App.tsx new file mode 100644 index 00000000..bda0665a --- /dev/null +++ b/reboot/examples/chick-potle/web/ui/cart/App.tsx @@ -0,0 +1,84 @@ +import { useState, type FC } from "react"; +import { useFoodOrder } from "@api/ai_chat_food/v1/food_rbt_react"; +import css from "./App.module.css"; + +export const CartApp: FC = () => { + const [pendingIndex, setPendingIndex] = useState(null); + const order = useFoodOrder(); + const { response: menuRes } = order.useGetMenu(); + const { response: cartRes, isLoading } = order.useGetCart(); + + if (isLoading && cartRes === undefined) { + return ( +
+
loading cart...
+
+ ); + } + + const items = menuRes?.items ?? []; + const entries = cartRes?.entries ?? []; + const totalCents = cartRes?.totalCents ?? 0; + + const formatPrice = (cents: number) => `$${(cents / 100).toFixed(2)}`; + + const handleRemove = async (itemIndex: number) => { + const itemName = items[itemIndex]?.name ?? "item"; + setPendingIndex(itemIndex); + try { + await order.removeFromCart({ itemIndex }); + } finally { + setPendingIndex(null); + } + }; + + if (entries.length === 0) { + return ( +
+
Your Order
+
+ Your cart is empty. Add some items from the menu! +
+
+ ); + } + + return ( +
+
Your Order
+
+ {entries.map((entry) => { + const item = items[entry.itemIndex]; + if (!item) return null; + const lineTotal = item.priceCents * entry.quantity; + return ( +
+
{item.emoji}
+
+
{item.name}
+
+ {formatPrice(item.priceCents)} x {entry.quantity} +
+
+
{formatPrice(lineTotal)}
+ +
+ ); + })} +
+
+ Total + {formatPrice(totalCents)} +
+
+ {entries.reduce((sum, e) => sum + e.quantity, 0)} items +
+
+ ); +}; diff --git a/reboot/examples/chick-potle/web/ui/cart/index.html b/reboot/examples/chick-potle/web/ui/cart/index.html new file mode 100644 index 00000000..fc41c81f --- /dev/null +++ b/reboot/examples/chick-potle/web/ui/cart/index.html @@ -0,0 +1,12 @@ + + + + + + Your Cart + + +
+ + + diff --git a/reboot/examples/chick-potle/web/ui/cart/main.tsx b/reboot/examples/chick-potle/web/ui/cart/main.tsx new file mode 100644 index 00000000..cbddb77e --- /dev/null +++ b/reboot/examples/chick-potle/web/ui/cart/main.tsx @@ -0,0 +1,13 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { RebootClientProvider } from "@reboot-dev/reboot-react"; +import { CartApp } from "./App"; +import "../../index.css"; + +createRoot(document.getElementById("root")!).render( + + + + + +); diff --git a/reboot/examples/chick-potle/web/ui/menu/App.module.css b/reboot/examples/chick-potle/web/ui/menu/App.module.css new file mode 100644 index 00000000..79b45114 --- /dev/null +++ b/reboot/examples/chick-potle/web/ui/menu/App.module.css @@ -0,0 +1,152 @@ +.container { + background: var(--cfa-white); + color: var(--cfa-dark); + font-family: var(--font-brand); + padding: 20px; +} + +.header { + font-size: 28px; + font-weight: 900; + text-transform: uppercase; + letter-spacing: 0.08em; + color: var(--cfa-red); + text-align: center; + line-height: 1; +} + +.subheader { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.25em; + color: var(--cfa-dark); + text-align: center; + margin-bottom: 20px; +} + +.section { + margin-bottom: 20px; + text-align: center; +} + +.categoryHeader { + font-size: 13px; + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--cfa-red); + margin-bottom: 8px; + padding-bottom: 6px; + border-bottom: 2px solid var(--cfa-red); + text-align: left; +} + +.grid { + display: inline-flex; + flex-wrap: wrap; + justify-content: center; + gap: 10px; +} + +.card { + flex: 0 0 220px; + background: var(--cfa-light-gray); + border: 1px solid var(--cfa-mid-gray); + border-radius: 8px; + overflow: hidden; + transition: border-color 0.15s ease, box-shadow 0.15s ease; + display: flex; + flex-direction: column; + text-align: left; +} + +.card:hover { + border-color: var(--cfa-red); + box-shadow: 0 2px 12px rgba(229, 22, 54, 0.15); +} + +.emojiArea { + display: flex; + align-items: center; + justify-content: center; + height: 80px; + font-size: 48px; + background: var(--cfa-white); +} + +.info { + padding: 10px; + display: flex; + flex-direction: column; + gap: 4px; + flex: 1; +} + +.name { + font-size: 14px; + font-weight: 800; + color: var(--cfa-dark); +} + +.description { + font-size: 10px; + color: var(--color-text-muted); + line-height: 1.5; +} + +.priceRow { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 4px; +} + +.price { + font-size: 15px; + font-weight: 800; + color: var(--cfa-dark); +} + +.inCart { + font-size: 11px; + font-weight: bold; + color: var(--cfa-red); + background: rgba(229, 22, 54, 0.1); + padding: 1px 6px; + border-radius: 3px; +} + +.addButton { + width: 100%; + padding: 8px; + font-size: 12px; + font-family: var(--font-brand); + font-weight: 800; + text-transform: uppercase; + letter-spacing: 0.05em; + background: var(--cfa-red); + color: var(--cfa-white); + border: none; + border-radius: 4px; + cursor: pointer; + transition: all 0.15s ease; + margin-top: auto; +} + +.addButton:hover:not(:disabled) { + background: var(--cfa-red-dark); + box-shadow: 0 2px 8px rgba(229, 22, 54, 0.3); +} + +.addButton:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +.loading { + color: var(--color-text-muted); + font-size: 12px; + text-align: center; + padding: 24px; +} diff --git a/reboot/examples/chick-potle/web/ui/menu/App.tsx b/reboot/examples/chick-potle/web/ui/menu/App.tsx new file mode 100644 index 00000000..14617078 --- /dev/null +++ b/reboot/examples/chick-potle/web/ui/menu/App.tsx @@ -0,0 +1,80 @@ +import { useState, type FC } from "react"; +import { useFoodOrder } from "@api/ai_chat_food/v1/food_rbt_react"; +import css from "./App.module.css"; + +export const MenuApp: FC = () => { + const [pendingIndex, setPendingIndex] = useState(null); + const order = useFoodOrder(); + const { response: menuRes, isLoading: menuLoading } = order.useGetMenu(); + const { response: cartRes } = order.useGetCart(); + + if (menuLoading && menuRes === undefined) { + return ( +
+
loading menu...
+
+ ); + } + + const items = menuRes?.items ?? []; + const cartEntries = cartRes?.entries ?? []; + + const getCartQty = (index: number) => { + const entry = cartEntries.find((e) => e.itemIndex === index); + return entry?.quantity ?? 0; + }; + + // Group items by category. + const categories = [...new Set(items.map((item) => item.category))]; + + const handleAdd = async (index: number) => { + setPendingIndex(index); + try { + await order.addToCart({ itemIndex: index, quantity: 1 }); + } finally { + setPendingIndex(null); + } + }; + + const formatPrice = (cents: number) => `$${(cents / 100).toFixed(2)}`; + + return ( +
+
Chick-potle
+
Delicious Mexican
+ {categories.map((cat) => ( +
+
{cat}
+
+ {items.map((item, i) => { + if (item.category !== cat) return null; + const qty = getCartQty(i); + return ( +
+
{item.emoji}
+
+
{item.name}
+
{item.description}
+
+ + {formatPrice(item.priceCents)} + + {qty > 0 && x{qty}} +
+ +
+
+ ); + })} +
+
+ ))} +
+ ); +}; diff --git a/reboot/examples/chick-potle/web/ui/menu/index.html b/reboot/examples/chick-potle/web/ui/menu/index.html new file mode 100644 index 00000000..c4e8a067 --- /dev/null +++ b/reboot/examples/chick-potle/web/ui/menu/index.html @@ -0,0 +1,12 @@ + + + + + + Food Menu + + +
+ + + diff --git a/reboot/examples/chick-potle/web/ui/menu/main.tsx b/reboot/examples/chick-potle/web/ui/menu/main.tsx new file mode 100644 index 00000000..a6817eb4 --- /dev/null +++ b/reboot/examples/chick-potle/web/ui/menu/main.tsx @@ -0,0 +1,13 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { RebootClientProvider } from "@reboot-dev/reboot-react"; +import { MenuApp } from "./App"; +import "../../index.css"; + +createRoot(document.getElementById("root")!).render( + + + + + +); diff --git a/reboot/examples/chick-potle/web/vite.config.ts b/reboot/examples/chick-potle/web/vite.config.ts new file mode 100644 index 00000000..d36251ae --- /dev/null +++ b/reboot/examples/chick-potle/web/vite.config.ts @@ -0,0 +1,60 @@ +import fs from "fs"; +import path from "path"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; +import { viteSingleFile } from "vite-plugin-singlefile"; + +const uiDir = path.resolve(__dirname, "ui"); +const uis: Record = + Object.fromEntries( + fs + .readdirSync(uiDir) + .filter((d) => fs.existsSync(path.join(uiDir, d, "index.html"))) + .map((name) => [ + name, + { input: `ui/${name}/index.html`, output: `${name}.html` }, + ]) + ); + +export default defineConfig(({ command, mode }) => { + const resolve = { + alias: { "@api": path.resolve(__dirname, "./api") }, + dedupe: ["react", "react-dom", "zod"], + }; + + if (command === "serve") { + const port = parseInt(process.env.RBT_VITE_PORT || "4444", 10); + return { + plugins: [react()], + root: ".", + resolve, + base: "/__/web/", + server: { port, strictPort: true, host: true, allowedHosts: true }, + }; + } + + const ui = uis[mode]; + if (!ui) { + const valid = Object.keys(uis).join(", "); + throw new Error(`Unknown UI: ${mode}. Use --mode with: ${valid}`); + } + + return { + plugins: [react(), viteSingleFile()], + build: { + outDir: "dist", + emptyOutDir: false, + assetsInlineLimit: 100000000, + cssCodeSplit: false, + rollupOptions: { + input: ui.input, + output: { + inlineDynamicImports: true, + entryFileNames: ui.output.replace(".html", ".js"), + assetFileNames: ui.output.replace(".html", ".[ext]"), + }, + }, + }, + resolve, + }; +}); diff --git a/reboot/examples/counter/.rbtrc b/reboot/examples/counter/.rbtrc index 6f2663e5..dcf4c706 100644 --- a/reboot/examples/counter/.rbtrc +++ b/reboot/examples/counter/.rbtrc @@ -3,7 +3,7 @@ generate --nodejs=api generate --react=api # Set the name of our application. -dev run --name=counter +dev run --application-name=counter # Declare that this is a nodejs application. dev run --nodejs diff --git a/reboot/examples/counter/README.md b/reboot/examples/counter/README.md index 42c5c104..60969478 100644 --- a/reboot/examples/counter/README.md +++ b/reboot/examples/counter/README.md @@ -10,12 +10,12 @@ For the impatient: A very simple (but still reactive and transactional!) counter. -The [Reboot '.proto' definitions](https://docs.reboot.dev/develop/define/overview/#code-generation) +The [Reboot '.proto' definitions](https://docs.reboot.dev/learn_more/define/overview#code-generation) can be found in the `api/` directory, grouped into subdirectories by proto package, while backend specific code can be found in `backend/` and front end specific code in `web/`. -_For more information on all of the Reboot examples, please [see the docs](https://docs.reboot.dev/get_started/examples)._ +_For more information on all of the Reboot examples, please [see the docs](https://docs.reboot.dev/full_stack_apps/examples)._ ## Prepare an environment by... diff --git a/reboot/examples/counter/package-lock.json b/reboot/examples/counter/package-lock.json index 4facd984..36e60536 100644 --- a/reboot/examples/counter/package-lock.json +++ b/reboot/examples/counter/package-lock.json @@ -9,8 +9,8 @@ "version": "0.0.0", "dependencies": { "@bufbuild/protobuf": "1.10.1", - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-react": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", "next": "14.2.13", "react": "^18.3.1", "react-dom": "^18.3.1" @@ -867,9 +867,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -1025,15 +1025,18 @@ } }, "node_modules/@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "license": "MIT", "workspaces": [ "examples/*" ], + "engines": { + "node": ">=20" + }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" @@ -1048,9 +1051,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -1088,9 +1091,9 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1343,15 +1346,15 @@ } }, "node_modules/@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -1380,9 +1383,9 @@ } }, "node_modules/@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0", @@ -1407,15 +1410,15 @@ } }, "node_modules/@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", "license": "Apache-2.0", "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -1442,12 +1445,12 @@ } }, "node_modules/@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -2163,9 +2166,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -3884,9 +3887,9 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -3941,9 +3944,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "license": "MIT", "dependencies": { "ip-address": "10.1.0" @@ -4474,9 +4477,9 @@ } }, "node_modules/hono": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", - "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==", + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -5079,9 +5082,9 @@ } }, "node_modules/jose": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", - "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -8842,12 +8845,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", "peerDependencies": { - "zod": "^3.25 || ^4" + "zod": "^3.25.28 || ^4" } } }, @@ -9324,9 +9327,9 @@ "dev": true }, "@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "requires": {} }, "@humanwhocodes/config-array": { @@ -9441,15 +9444,15 @@ } }, "@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "requires": {} }, "@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "requires": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -9471,9 +9474,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -9622,13 +9625,13 @@ "optional": true }, "@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "requires": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -9645,9 +9648,9 @@ } }, "@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "requires": { "@scarf/scarf": "1.4.0", "typescript": "5.4.5", @@ -9662,14 +9665,14 @@ } }, "@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", - "requires": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", + "requires": { + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -9686,11 +9689,11 @@ } }, "@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "requires": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -10155,9 +10158,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -11421,9 +11424,9 @@ } }, "eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==" }, "exponential-backoff": { "version": "3.1.1", @@ -11466,9 +11469,9 @@ } }, "express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "requires": { "ip-address": "10.1.0" }, @@ -11836,9 +11839,9 @@ } }, "hono": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", - "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==" + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==" }, "http-cache-semantics": { "version": "4.1.1", @@ -12237,9 +12240,9 @@ "dev": true }, "jose": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", - "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==" + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==" }, "js-sha1": { "version": "0.7.0", @@ -14603,9 +14606,9 @@ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==" }, "zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "requires": {} } } diff --git a/reboot/examples/counter/package.json b/reboot/examples/counter/package.json index c12c093a..249bfe91 100644 --- a/reboot/examples/counter/package.json +++ b/reboot/examples/counter/package.json @@ -11,8 +11,8 @@ }, "dependencies": { "@bufbuild/protobuf": "1.10.1", - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-react": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", "next": "14.2.13", "react": "^18.3.1", "react-dom": "^18.3.1" diff --git a/reboot/examples/docubot/.rbtrc b/reboot/examples/docubot/.rbtrc index b1ff931b..73088287 100644 --- a/reboot/examples/docubot/.rbtrc +++ b/reboot/examples/docubot/.rbtrc @@ -33,10 +33,10 @@ generate --nodejs=api # or 'bundler'. We choose 'nodenext' which requires imports with file extensions. generate --nodejs-extensions -dev run --name=docubot +dev run --application-name=docubot dev run --nodejs dev run --application=backend/src/main.ts -dev expunge --name=docubot +dev expunge --application-name=docubot diff --git a/reboot/examples/docubot/README.md b/reboot/examples/docubot/README.md index 5d2e1204..f513df4a 100644 --- a/reboot/examples/docubot/README.md +++ b/reboot/examples/docubot/README.md @@ -12,12 +12,12 @@ This repository contains code for Docubot, an OpenAI RAG app that ingests help center documents and creates a chatbot that can answer natural language questions about the docs. -The [Reboot '.proto' definitions](https://docs.reboot.dev/develop/define/overview/#code-generation) +The [Reboot '.proto' definitions](https://docs.reboot.dev/learn_more/define/overview#code-generation) can be found in the `api/` directory, grouped into subdirectories by proto package, while backend specific code can be found in `backend/` and front end specific code in `web/`. -_For more information on all of the Reboot examples, please [see the docs](https://docs.reboot.dev/get_started/examples)._ +_For more information on all of the Reboot examples, please [see the docs](https://docs.reboot.dev/full_stack_apps/examples)._ ## Prepare an environment by... diff --git a/reboot/examples/docubot/api/package.json b/reboot/examples/docubot/api/package.json index 546318f0..6ab0490a 100644 --- a/reboot/examples/docubot/api/package.json +++ b/reboot/examples/docubot/api/package.json @@ -7,7 +7,7 @@ "prepack": "rbt generate && tsc" }, "dependencies": { - "@reboot-dev/reboot": "0.46.0", + "@reboot-dev/reboot": "1.0.3", "typescript": "^5.2.2" }, "files": [ diff --git a/reboot/examples/docubot/docubot/package.json b/reboot/examples/docubot/docubot/package.json index 9004eb29..36fac4d7 100644 --- a/reboot/examples/docubot/docubot/package.json +++ b/reboot/examples/docubot/docubot/package.json @@ -8,8 +8,8 @@ }, "dependencies": { "@reboot-dev/docubot-api": "0.1.0", - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", "create-temp-directory": "^2.4.0", "openai": "^4.52.7", "puppeteer": "^22.14.0", diff --git a/reboot/examples/docubot/package-lock.json b/reboot/examples/docubot/package-lock.json index ffebd8bc..0c3ab292 100644 --- a/reboot/examples/docubot/package-lock.json +++ b/reboot/examples/docubot/package-lock.json @@ -18,9 +18,9 @@ "@radix-ui/react-slot": "^1.0.2", "@reboot-dev/docubot": "workspace:*", "@reboot-dev/docubot-api": "workspace:*", - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-react": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "framer-motion": "^11.0.24", @@ -54,7 +54,7 @@ "name": "@reboot-dev/docubot-api", "version": "0.1.0", "dependencies": { - "@reboot-dev/reboot": "0.46.0", + "@reboot-dev/reboot": "1.0.3", "typescript": "^5.2.2" } }, @@ -63,8 +63,8 @@ "version": "0.1.0", "dependencies": { "@reboot-dev/docubot-api": "0.1.0", - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", "create-temp-directory": "^2.4.0", "openai": "^4.52.7", "puppeteer": "^22.14.0", @@ -1080,9 +1080,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -1387,9 +1387,9 @@ "dev": true }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -1427,9 +1427,9 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1458,12 +1458,12 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", "peerDependencies": { - "zod": "^3.25 || ^4" + "zod": "^3.25.28 || ^4" } }, "node_modules/@nodelib/fs.scandir": { @@ -1651,15 +1651,15 @@ "link": true }, "node_modules/@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -1688,9 +1688,9 @@ } }, "node_modules/@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0", @@ -1722,15 +1722,15 @@ } }, "node_modules/@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", "license": "Apache-2.0", "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -1745,15 +1745,18 @@ } }, "node_modules/@reboot-dev/reboot-react/node_modules/@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "license": "MIT", "workspaces": [ "examples/*" ], + "engines": { + "node": ">=20" + }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" @@ -1790,12 +1793,12 @@ } }, "node_modules/@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -4428,9 +4431,9 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -4485,9 +4488,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "license": "MIT", "dependencies": { "ip-address": "10.1.0" @@ -5163,9 +5166,9 @@ } }, "node_modules/hono": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", - "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==", + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -5534,9 +5537,9 @@ "dev": true }, "node_modules/jose": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", - "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -10958,9 +10961,9 @@ "dev": true }, "@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "requires": {} }, "@humanwhocodes/config-array": { @@ -11199,9 +11202,9 @@ } }, "@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "requires": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", @@ -11223,9 +11226,9 @@ }, "dependencies": { "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "requires": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -11244,9 +11247,9 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==" }, "zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "requires": {} } } @@ -11362,8 +11365,8 @@ "version": "file:docubot", "requires": { "@reboot-dev/docubot-api": "0.1.0", - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", "@types/node": "^20.12.4", "create-temp-directory": "^2.4.0", "openai": "^4.52.7", @@ -11383,18 +11386,18 @@ "@reboot-dev/docubot-api": { "version": "file:api", "requires": { - "@reboot-dev/reboot": "0.46.0", + "@reboot-dev/reboot": "1.0.3", "typescript": "^5.2.2" } }, "@reboot-dev/reboot": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-0.46.0.tgz", - "integrity": "sha512-mRRV5ItpZTibfnn9uP3R3S4iTgOJa3ekvA9EHYzB0KD+iVXjM7HDPdx4LuMQAPS3U1afcd2xakBmXcgJx3jBzw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot/-/reboot-1.0.3.tgz", + "integrity": "sha512-k/fw5iMY4RdC7HMqdXkjPkdc1HOqi6vsw3oh9SpSjvIqSrsvr576bNE9DzOfrpWjhN/l2s8Z5Mmgv8An7COyOQ==", "requires": { "@bufbuild/protoc-gen-es": "1.10.1", "@bufbuild/protoplugin": "1.10.1", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "@standard-schema/spec": "1.0.0", "chalk": "^4.1.2", @@ -11418,9 +11421,9 @@ } }, "@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "requires": { "@scarf/scarf": "1.4.0", "typescript": "5.4.5", @@ -11440,14 +11443,14 @@ } }, "@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", - "requires": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", + "requires": { + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -11457,9 +11460,9 @@ }, "dependencies": { "@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "requires": {} }, "typescript": { @@ -11476,11 +11479,11 @@ } }, "@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "requires": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -13292,9 +13295,9 @@ } }, "eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==" + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==" }, "exponential-backoff": { "version": "3.1.1", @@ -13352,9 +13355,9 @@ } }, "express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "requires": { "ip-address": "10.1.0" }, @@ -13805,9 +13808,9 @@ "dev": true }, "hono": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", - "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==" + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==" }, "html-url-attributes": { "version": "3.0.1", @@ -14055,9 +14058,9 @@ "dev": true }, "jose": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", - "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==" + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==" }, "js-sha1": { "version": "0.7.0", diff --git a/reboot/examples/docubot/package.json b/reboot/examples/docubot/package.json index 9406cdee..8b1c4ee9 100644 --- a/reboot/examples/docubot/package.json +++ b/reboot/examples/docubot/package.json @@ -19,9 +19,9 @@ "@radix-ui/react-slot": "^1.0.2", "@reboot-dev/docubot": "workspace:*", "@reboot-dev/docubot-api": "workspace:*", - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-react": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "framer-motion": "^11.0.24", diff --git a/reboot/examples/kcdc-2025/.rbtrc b/reboot/examples/kcdc-2025/.rbtrc index e98a2f5c..5cebbaae 100644 --- a/reboot/examples/kcdc-2025/.rbtrc +++ b/reboot/examples/kcdc-2025/.rbtrc @@ -12,10 +12,10 @@ dev run --watch=backend/**/*.py dev run --python # Save state between chaos restarts. -dev run --name=chat +dev run --application-name=chat # Run the application! dev run --application=backend/src/main.py # When expunging, expunge that state we've saved. -dev expunge --name=chat +dev expunge --application-name=chat diff --git a/reboot/examples/kcdc-2025/backend/src/servicers/chatbot.py b/reboot/examples/kcdc-2025/backend/src/servicers/chatbot.py index d7620252..4c4679d8 100644 --- a/reboot/examples/kcdc-2025/backend/src/servicers/chatbot.py +++ b/reboot/examples/kcdc-2025/backend/src/servicers/chatbot.py @@ -177,9 +177,7 @@ async def generate() -> ChatbotResponse: ) try: - response = await at_most_once( - "Generate", context, generate, type=ChatbotResponse - ) + response = await at_most_once("Generate", context, generate) if response.should_respond: text = response.response diff --git a/reboot/examples/kcdc-2025/pyproject.toml b/reboot/examples/kcdc-2025/pyproject.toml index e22a1928..743558c3 100644 --- a/reboot/examples/kcdc-2025/pyproject.toml +++ b/reboot/examples/kcdc-2025/pyproject.toml @@ -5,13 +5,13 @@ requires-python = ">= 3.10" dependencies = [ "langchain>=0.3.27", "langchain-anthropic>=0.3.18", - "reboot==0.46.0", + "reboot==1.0.3", ] [tool.rye] dev-dependencies = [ "mypy==1.18.1", "types-protobuf>=4.24.0.20240129", - "reboot==0.46.0", + "reboot==1.0.3", ] # This project only uses `rye` to provide `python` and its dependencies, so diff --git a/reboot/examples/kcdc-2025/requirements-dev.lock b/reboot/examples/kcdc-2025/requirements-dev.lock index be0338b8..ad46434a 100644 --- a/reboot/examples/kcdc-2025/requirements-dev.lock +++ b/reboot/examples/kcdc-2025/requirements-dev.lock @@ -49,6 +49,7 @@ charset-normalizer==3.4.3 click==8.2.1 # via uvicorn colorama==0.4.6 + # via griffecli # via reboot cryptography==44.0.0 # via pyjwt @@ -61,17 +62,27 @@ distro==1.9.0 # via anthropic exceptiongroup==1.3.0 # via anyio + # via pydantic-ai-slim fastapi==0.115.12 # via reboot frozenlist==1.7.0 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot greenlet==3.2.4 # via sqlalchemy +griffe==2.0.2 + # via pydantic-ai-slim +griffecli==2.0.2 + # via griffe +griffelib==2.0.2 + # via griffe + # via griffecli grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -101,8 +112,11 @@ httpcore==1.0.9 # via httpx httpx==0.28.1 # via anthropic + # via genai-prices # via langsmith # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 @@ -142,9 +156,11 @@ langchain-text-splitters==0.3.9 langsmith==0.4.14 # via langchain # via langchain-core +logfire-api==4.32.1 + # via pydantic-graph markupsafe==3.0.2 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot multidict==6.6.4 # via aiohttp @@ -160,6 +176,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -208,15 +225,22 @@ pycparser==2.22 pydantic==2.11.7 # via anthropic # via fastapi + # via genai-prices # via langchain # via langchain-anthropic # via langchain-core # via langsmith # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot +pydantic-ai-slim==1.60.0 + # via reboot pydantic-core==2.33.2 # via pydantic +pydantic-graph==1.60.0 + # via pydantic-ai-slim pydantic-settings==2.13.1 # via mcp pyjwt==2.10.1 @@ -237,7 +261,7 @@ pyyaml==6.0.2 # via langchain # via langchain-core # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications @@ -274,7 +298,7 @@ tomli==2.2.1 # via mypy types-protobuf==6.30.2.20250809 # via mypy-protobuf -typing-extensions==4.14.1 +typing-extensions==4.15.0 # via aiosignal # via anthropic # via anyio @@ -287,6 +311,7 @@ typing-extensions==4.14.1 # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via sqlalchemy # via typing-inspection @@ -294,6 +319,8 @@ typing-extensions==4.14.1 typing-inspection==0.4.1 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot diff --git a/reboot/examples/kcdc-2025/requirements.lock b/reboot/examples/kcdc-2025/requirements.lock index c32c7d73..2d6a42bf 100644 --- a/reboot/examples/kcdc-2025/requirements.lock +++ b/reboot/examples/kcdc-2025/requirements.lock @@ -49,6 +49,7 @@ charset-normalizer==3.4.3 click==8.2.1 # via uvicorn colorama==0.4.6 + # via griffecli # via reboot cryptography==44.0.0 # via pyjwt @@ -61,17 +62,27 @@ distro==1.9.0 # via anthropic exceptiongroup==1.3.0 # via anyio + # via pydantic-ai-slim fastapi==0.115.12 # via reboot frozenlist==1.7.0 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot greenlet==3.2.4 # via sqlalchemy +griffe==2.0.2 + # via pydantic-ai-slim +griffecli==2.0.2 + # via griffe +griffelib==2.0.2 + # via griffe + # via griffecli grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -101,8 +112,11 @@ httpcore==1.0.9 # via httpx httpx==0.28.1 # via anthropic + # via genai-prices # via langsmith # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 @@ -142,9 +156,11 @@ langchain-text-splitters==0.3.9 langsmith==0.4.14 # via langchain # via langchain-core +logfire-api==4.32.1 + # via pydantic-graph markupsafe==3.0.2 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot multidict==6.6.4 # via aiohttp @@ -157,6 +173,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -204,15 +221,22 @@ pycparser==2.22 pydantic==2.11.7 # via anthropic # via fastapi + # via genai-prices # via langchain # via langchain-anthropic # via langchain-core # via langsmith # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot +pydantic-ai-slim==1.60.0 + # via reboot pydantic-core==2.33.2 # via pydantic +pydantic-graph==1.60.0 + # via pydantic-ai-slim pydantic-settings==2.13.1 # via mcp pyjwt==2.10.1 @@ -233,7 +257,7 @@ pyyaml==6.0.2 # via langchain # via langchain-core # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications @@ -268,7 +292,7 @@ tenacity==9.1.2 # via langchain-core types-protobuf==6.30.2.20250809 # via mypy-protobuf -typing-extensions==4.14.1 +typing-extensions==4.15.0 # via aiosignal # via anthropic # via anyio @@ -280,6 +304,7 @@ typing-extensions==4.14.1 # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via sqlalchemy # via typing-inspection @@ -287,6 +312,8 @@ typing-extensions==4.14.1 typing-inspection==0.4.1 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot diff --git a/reboot/examples/kcdc-2025/web/package-lock.json b/reboot/examples/kcdc-2025/web/package-lock.json index 9abee58a..117dc1f3 100644 --- a/reboot/examples/kcdc-2025/web/package-lock.json +++ b/reboot/examples/kcdc-2025/web/package-lock.json @@ -16,9 +16,9 @@ "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.7", - "@reboot-dev/reboot-react": "0.46.0", - "@reboot-dev/reboot-std-api": "0.46.0", - "@reboot-dev/reboot-std-react": "0.46.0", + "@reboot-dev/reboot-react": "1.0.3", + "@reboot-dev/reboot-std-api": "1.0.3", + "@reboot-dev/reboot-std-react": "1.0.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "emoji-picker-react": "^4.9.2", @@ -895,9 +895,9 @@ "license": "MIT" }, "node_modules/@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -1048,15 +1048,18 @@ } }, "node_modules/@modelcontextprotocol/ext-apps": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.2.0.tgz", - "integrity": "sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.5.0.tgz", + "integrity": "sha512-q4fut89TOoP2LEPHSGfZErIf1K1xOTTzV+41h/bB2BqKw2gKb0uLKbHusOy1UtbY0puS16zBho/vFp3f5XMVbQ==", "license": "MIT", "workspaces": [ "examples/*" ], + "engines": { + "node": ">=20" + }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0", + "@modelcontextprotocol/sdk": "^1.29.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "zod": "^3.25.0 || ^4.0.0" @@ -1071,9 +1074,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", - "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -1111,9 +1114,9 @@ } }, "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1750,9 +1753,9 @@ "license": "MIT" }, "node_modules/@reboot-dev/reboot-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-0.46.0.tgz", - "integrity": "sha512-GGKEqcMHRvV67eoU7W5JitOgs3vrM6i1naxHJlZcOMjw/BHQXcYNAudX3ZfcRpJWfvJ0An+7tT7sStfAGCXhog==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-api/-/reboot-api-1.0.3.tgz", + "integrity": "sha512-oivrFr4nbT3ByBQ+SAvfeBWPsSFu1Xii4LbBAKb1nBiiYlxZ0Q0ERDpSTTcv3xVtzrcIjSbXG22Hftk1hlRovg==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0", @@ -1786,15 +1789,15 @@ } }, "node_modules/@reboot-dev/reboot-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-0.46.0.tgz", - "integrity": "sha512-v9fo5ZxgL4dpXvUWHWhbu6n4lO3koWuEKukNRpZ53cTZFzNlbghkA58z39Hh/Q13pvefK9NsnvCPFsWYDZCQrA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-react/-/reboot-react-1.0.3.tgz", + "integrity": "sha512-vLDI3bVAv/0O5cYNVl3ard4onfTEDOHSkQ6Q1ylqs4E/rfGatsL6HtJYEqHVwkRQ5RXNaaJWV5f2V64dAWFsnQ==", "license": "Apache-2.0", "dependencies": { - "@modelcontextprotocol/ext-apps": "1.2.0", - "@modelcontextprotocol/sdk": "1.27.1", - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "@modelcontextprotocol/ext-apps": "1.5.0", + "@modelcontextprotocol/sdk": "1.29.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0", "@types/uuid": "^9.0.4", "js-sha1": "0.7.0", @@ -1822,34 +1825,34 @@ } }, "node_modules/@reboot-dev/reboot-std-api": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-api/-/reboot-std-api-0.46.0.tgz", - "integrity": "sha512-SPt7RsLdJoGxxrf/896yC7/lqBNqVmAPL2BFpZbyCV+Bb205VAodm7ERylhvaFEU7PgucKOrjR7WXXSTeXDo6w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-api/-/reboot-std-api-1.0.3.tgz", + "integrity": "sha512-k6aRTEuL40ki4FN51A7UdkvGK2bAOJ5npStWkL3tpLhIwtmcmdtbNZZeYEdVgPCLEgReIep9bd61U6ObnNzXtA==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "1.4.0" } }, "node_modules/@reboot-dev/reboot-std-react": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-react/-/reboot-std-react-0.46.0.tgz", - "integrity": "sha512-QVQj7DsjsbdXAPmIRN3xrjnabYfB1nIk00FQxEs31sXQwdP1Mcr0QddwzyhrtoX84t5+HNV5YZWz2vP+PLcQQg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-std-react/-/reboot-std-react-1.0.3.tgz", + "integrity": "sha512-EcbLjY5BvaGa5XIyyVRN0z/wUSHbR+rclcMqI27GddGlk7j9yz4Bn7FI40s7QZB9shuGdiVjp//S6GB4lZ/yvw==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot-api": "0.46.0", - "@reboot-dev/reboot-react": "0.46.0", - "@reboot-dev/reboot-std-api": "0.46.0", - "@reboot-dev/reboot-web": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", + "@reboot-dev/reboot-react": "1.0.3", + "@reboot-dev/reboot-std-api": "1.0.3", + "@reboot-dev/reboot-web": "1.0.3", "@scarf/scarf": "1.4.0" } }, "node_modules/@reboot-dev/reboot-web": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-0.46.0.tgz", - "integrity": "sha512-NoIFbgX3hdGWOnuRSFE2W1xwsM5Le4MmwztoarlOsbfh3ATCMUSqrR7OspIzBpo6vgKYM5wO1QdQZA7GwUqLYQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@reboot-dev/reboot-web/-/reboot-web-1.0.3.tgz", + "integrity": "sha512-mFXgoOTUhUrlXs9p1g4vZCu33Dv9ze4ayN4X5ES8F9zkP+O4rweC/aQAalzpzEPA+Mk6HIUUNADUT2Lo1C4c0A==", "license": "Apache-2.0", "dependencies": { - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot-api": "1.0.3", "@scarf/scarf": "1.4.0", "js-sha1": "0.7.0", "lru-cache-idb": "^0.5.2", @@ -2556,9 +2559,9 @@ } }, "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -2976,9 +2979,9 @@ "license": "MIT" }, "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "license": "MIT", "engines": { "node": ">=18" @@ -3530,9 +3533,9 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -3582,9 +3585,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.4.1.tgz", + "integrity": "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw==", "license": "MIT", "dependencies": { "ip-address": "10.1.0" @@ -4046,9 +4049,9 @@ } }, "node_modules/hono": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.5.tgz", - "integrity": "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg==", + "version": "4.12.15", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.15.tgz", + "integrity": "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -4276,9 +4279,9 @@ } }, "node_modules/jose": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.1.tgz", - "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -4829,9 +4832,9 @@ "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", "funding": { "type": "opencollective", @@ -5070,9 +5073,9 @@ } }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -5518,13 +5521,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -6319,12 +6322,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", "peerDependencies": { - "zod": "^3.25 || ^4" + "zod": "^3.25.28 || ^4" } } } diff --git a/reboot/examples/kcdc-2025/web/package.json b/reboot/examples/kcdc-2025/web/package.json index 249c5c49..78a5e69f 100644 --- a/reboot/examples/kcdc-2025/web/package.json +++ b/reboot/examples/kcdc-2025/web/package.json @@ -18,9 +18,9 @@ "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.7", - "@reboot-dev/reboot-react": "0.46.0", - "@reboot-dev/reboot-std-api": "0.46.0", - "@reboot-dev/reboot-std-react": "0.46.0", + "@reboot-dev/reboot-react": "1.0.3", + "@reboot-dev/reboot-std-api": "1.0.3", + "@reboot-dev/reboot-std-react": "1.0.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "emoji-picker-react": "^4.9.2", diff --git a/reboot/examples/monorepo/README.md b/reboot/examples/monorepo/README.md index 45a4e80c..32152e1f 100644 --- a/reboot/examples/monorepo/README.md +++ b/reboot/examples/monorepo/README.md @@ -13,12 +13,12 @@ examples are structured in the style of a monorepo: all proto files can be found in the `api/` directory, grouped into subdirectories by proto package, while application code is broken into top-level directories by application name. -The [Reboot '.proto' definitions](https://docs.reboot.dev/develop/define/overview/#code-generation) +The [Reboot '.proto' definitions](https://docs.reboot.dev/learn_more/define/overview#code-generation) can be found in the `api/` directory, grouped into subdirectories by proto package, while backend specific code can be found in top-level directories by application name. -_For more information on all of the Reboot examples, please [see the docs](https://docs.reboot.dev/get_started/examples)._ +_For more information on all of the Reboot examples, please [see the docs](https://docs.reboot.dev/full_stack_apps/examples)._ ## Prepare an environment by... diff --git a/reboot/examples/monorepo/hello-constructors/README.md b/reboot/examples/monorepo/hello-constructors/README.md index 827d6006..ecc0570b 100644 --- a/reboot/examples/monorepo/hello-constructors/README.md +++ b/reboot/examples/monorepo/hello-constructors/README.md @@ -8,4 +8,4 @@ In "Hello, Constructors" a state machine can't deliver messages until that state machine has been created by calling its constructor method, giving it an initial message to display. -Read more about constructors in the [Reboot documentation](https://docs.reboot.dev/develop/call/overview/#constructing-instances). +Read more about constructors in the [Reboot documentation](https://docs.reboot.dev/learn_more/call/overview#constructing-instances). diff --git a/reboot/examples/monorepo/hello-legacy-grpc/backend/src/reboot_greeter_servicer.py b/reboot/examples/monorepo/hello-legacy-grpc/backend/src/reboot_greeter_servicer.py index 22b8fe54..1727f738 100644 --- a/reboot/examples/monorepo/hello-legacy-grpc/backend/src/reboot_greeter_servicer.py +++ b/reboot/examples/monorepo/hello-legacy-grpc/backend/src/reboot_greeter_servicer.py @@ -33,7 +33,7 @@ async def _get_deprecated_salutation(self, context: Context) -> str: # WARNING: Calls to legacy gRPC services will not get the same safety # guarantees as Reboot calls. Calls from Reboot state machines to # legacy services should be wrapped in Tasks if they represent - # side-effects. See https://docs.reboot.dev/develop/side_effects. + # side-effects. See https://docs.reboot.dev/learn_more/side_effects. # # In this example, `DeprecatedGreeter`'s `GetSalutation` RPC # is a pure function, so it is safe to access from our context. diff --git a/reboot/examples/monorepo/hello-tasks/README.md b/reboot/examples/monorepo/hello-tasks/README.md index 1184d27d..8db89bc9 100644 --- a/reboot/examples/monorepo/hello-tasks/README.md +++ b/reboot/examples/monorepo/hello-tasks/README.md @@ -7,5 +7,5 @@ tasks. In "Hello, Tasks," all messages saved in a Hello state machine will disappear after 10 seconds, with a warning displayed after 7 seconds. -Read more about async tasks in the [Reboot documentation](https://docs.reboot.dev/develop/tasks/#scheduling-and-spawning). +Read more about async tasks in the [Reboot documentation](https://docs.reboot.dev/learn_more/tasks#scheduling-and-spawning). diff --git a/reboot/examples/monorepo/pyproject.toml b/reboot/examples/monorepo/pyproject.toml index 142168e1..1c50821b 100644 --- a/reboot/examples/monorepo/pyproject.toml +++ b/reboot/examples/monorepo/pyproject.toml @@ -1,7 +1,7 @@ [project] requires-python = ">= 3.10" dependencies = [ - "reboot==0.46.0", + "reboot==1.0.3", ] [tool.rye] @@ -9,7 +9,7 @@ dev-dependencies = [ "mypy==1.18.1", "pytest>=7.4.2", "types-protobuf>=4.24.0.20240129", - "reboot==0.46.0", + "reboot==1.0.3", ] # This project only uses `rye` to provide `python` and its dependencies, so diff --git a/reboot/examples/monorepo/requirements-dev.lock b/reboot/examples/monorepo/requirements-dev.lock index 31a9f3ca..8b72ef23 100644 --- a/reboot/examples/monorepo/requirements-dev.lock +++ b/reboot/examples/monorepo/requirements-dev.lock @@ -52,16 +52,21 @@ deprecated==1.2.15 # via opentelemetry-semantic-conventions exceptiongroup==1.2.2 # via anyio + # via pydantic-ai-slim # via pytest fastapi==0.115.12 # via reboot frozenlist==1.4.1 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -90,7 +95,10 @@ hpack==4.1.0 httpcore==1.0.8 # via httpx httpx==0.28.1 + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 @@ -114,9 +122,11 @@ jsonschema-specifications==2025.9.1 # via jsonschema kubernetes-asyncio==31.1.0 # via reboot +logfire-api==4.32.1 + # via pydantic-graph markupsafe==2.1.5 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot multidict==6.1.0 # via aiohttp @@ -132,6 +142,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -178,11 +189,18 @@ pycparser==2.22 # via cffi pydantic==2.12.5 # via fastapi + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot +pydantic-ai-slim==1.87.0 + # via reboot pydantic-core==2.41.5 # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim pydantic-settings==2.13.1 # via mcp pyjwt==2.10.1 @@ -202,7 +220,7 @@ python-ulid==3.1.0 pyyaml==6.0.2 # via kubernetes-asyncio # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications @@ -239,12 +257,15 @@ typing-extensions==4.15.0 # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via typing-inspection # via uvicorn typing-inspection==0.4.2 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot diff --git a/reboot/examples/monorepo/requirements.lock b/reboot/examples/monorepo/requirements.lock index 29a12988..e9cd6645 100644 --- a/reboot/examples/monorepo/requirements.lock +++ b/reboot/examples/monorepo/requirements.lock @@ -52,15 +52,20 @@ deprecated==1.2.15 # via opentelemetry-semantic-conventions exceptiongroup==1.2.2 # via anyio + # via pydantic-ai-slim fastapi==0.115.12 # via reboot frozenlist==1.4.1 # via aiohttp # via aiosignal +genai-prices==0.0.57 + # via pydantic-ai-slim googleapis-common-protos==1.65.0 # via grpcio-status # via opentelemetry-exporter-otlp-proto-grpc # via reboot +griffelib==2.0.2 + # via pydantic-ai-slim grpc-interceptor==0.15.4 # via reboot grpcio==1.64.3 @@ -89,7 +94,10 @@ hpack==4.1.0 httpcore==1.0.8 # via httpx httpx==0.28.1 + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph httpx-sse==0.4.3 # via mcp hyperframe==6.1.0 @@ -111,9 +119,11 @@ jsonschema-specifications==2025.9.1 # via jsonschema kubernetes-asyncio==31.1.0 # via reboot +logfire-api==4.32.1 + # via pydantic-graph markupsafe==2.1.5 # via jinja2 -mcp==1.26.0 +mcp==1.27.0 # via reboot multidict==6.1.0 # via aiohttp @@ -126,6 +136,7 @@ opentelemetry-api==1.28.1 # via opentelemetry-instrumentation-grpc # via opentelemetry-sdk # via opentelemetry-semantic-conventions + # via pydantic-ai-slim # via reboot opentelemetry-exporter-otlp-proto-common==1.28.1 # via opentelemetry-exporter-otlp-proto-grpc @@ -168,11 +179,18 @@ pycparser==2.22 # via cffi pydantic==2.12.5 # via fastapi + # via genai-prices # via mcp + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings # via reboot +pydantic-ai-slim==1.87.0 + # via reboot pydantic-core==2.41.5 # via pydantic +pydantic-graph==1.87.0 + # via pydantic-ai-slim pydantic-settings==2.13.1 # via mcp pyjwt==2.10.1 @@ -191,7 +209,7 @@ python-ulid==3.1.0 pyyaml==6.0.2 # via kubernetes-asyncio # via reboot -reboot==0.46.0 +reboot==1.0.3 referencing==0.37.0 # via jsonschema # via jsonschema-specifications @@ -224,12 +242,15 @@ typing-extensions==4.15.0 # via opentelemetry-sdk # via pydantic # via pydantic-core + # via reboot # via referencing # via typing-inspection # via uvicorn typing-inspection==0.4.2 # via mcp # via pydantic + # via pydantic-ai-slim + # via pydantic-graph # via pydantic-settings tzlocal==5.3 # via reboot diff --git a/reboot/examples/prosemirror-zod/Dockerfile b/reboot/examples/prosemirror-zod/Dockerfile index 4b856125..6bd5cafb 100644 --- a/reboot/examples/prosemirror-zod/Dockerfile +++ b/reboot/examples/prosemirror-zod/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/reboot-dev/reboot-base:0.46.0 +FROM ghcr.io/reboot-dev/reboot-base:1.0.3 WORKDIR /app diff --git a/reboot/examples/prosemirror-zod/README.md b/reboot/examples/prosemirror-zod/README.md index 004847d8..e18272ef 100644 --- a/reboot/examples/prosemirror-zod/README.md +++ b/reboot/examples/prosemirror-zod/README.md @@ -1,6 +1,6 @@ # Reboot + ProseMirror (w/ Zod) -(0) We suggest you use a GitHub Codespace (just click button below) _otherwise_ follow instructions [here](https://docs.reboot.dev/develop/define/overview) for getting your own environment set up. +(0) We suggest you use a GitHub Codespace (just click button below) _otherwise_ follow instructions [here](https://docs.reboot.dev/learn_more/define/overview) for getting your own environment set up. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/reboot-dev/reboot-prosemirror-zod) diff --git a/reboot/examples/prosemirror-zod/backend/.rbtrc b/reboot/examples/prosemirror-zod/backend/.rbtrc index 354bdc73..96cf2489 100644 --- a/reboot/examples/prosemirror-zod/backend/.rbtrc +++ b/reboot/examples/prosemirror-zod/backend/.rbtrc @@ -32,15 +32,15 @@ generate --react=../api dev run --nodejs # Save state between chaos restarts. -dev run --name=docs +dev run --application-name=docs # Run the application! dev run --application=src/main.ts # Use the same name to expunge that we do to run. -dev expunge --name=docs +dev expunge --application-name=docs -serve run --name=docs +serve run --application-name=docs # Tell `rbt serve` that this is a Node.js application. serve run --nodejs diff --git a/reboot/examples/prosemirror-zod/backend/package.json b/reboot/examples/prosemirror-zod/backend/package.json index 24451fca..0cfa9ee5 100644 --- a/reboot/examples/prosemirror-zod/backend/package.json +++ b/reboot/examples/prosemirror-zod/backend/package.json @@ -10,8 +10,8 @@ "@bufbuild/protobuf": "1.10.1", "@monorepo/api": "workspace:*", "@monorepo/common": "workspace:*", - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", "prosemirror-model": "^1.23.0", "prosemirror-transform": "^1.1.0" } diff --git a/reboot/examples/prosemirror-zod/web/package.json b/reboot/examples/prosemirror-zod/web/package.json index f3e9bab0..69400130 100644 --- a/reboot/examples/prosemirror-zod/web/package.json +++ b/reboot/examples/prosemirror-zod/web/package.json @@ -14,7 +14,7 @@ "@monorepo/api": "workspace:*", "@monorepo/common": "workspace:*", "@nytimes/react-prosemirror": "^0.6.2", - "@reboot-dev/reboot-react": "0.46.0", + "@reboot-dev/reboot-react": "1.0.3", "next": "15.0.5", "prosemirror-collab": "^1.3.1", "prosemirror-history": "^1.4.1", diff --git a/reboot/examples/prosemirror-zod/yarn.lock b/reboot/examples/prosemirror-zod/yarn.lock index 5a027d83..a33d9e11 100644 --- a/reboot/examples/prosemirror-zod/yarn.lock +++ b/reboot/examples/prosemirror-zod/yarn.lock @@ -740,11 +740,11 @@ __metadata: languageName: node linkType: hard -"@modelcontextprotocol/ext-apps@npm:1.2.0": - version: 1.2.0 - resolution: "@modelcontextprotocol/ext-apps@npm:1.2.0" +"@modelcontextprotocol/ext-apps@npm:1.5.0": + version: 1.5.0 + resolution: "@modelcontextprotocol/ext-apps@npm:1.5.0" peerDependencies: - "@modelcontextprotocol/sdk": ^1.24.0 + "@modelcontextprotocol/sdk": ^1.29.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 zod: ^3.25.0 || ^4.0.0 @@ -753,13 +753,13 @@ __metadata: optional: true react-dom: optional: true - checksum: 10c0/0fa70e5e6a7218579448e4b78880ab83b9c70c6f0fd56dfddd73c8b93c6be4fc1af79266f59b4245c4aea055455517095c7055013841439345804f5ac5cb14a8 + checksum: 10c0/b8ef86ee48fa5658c152a4ad282ab144335be9d25362a475c834155495d9b2884b1c524703df8ee599d43789c4ea92de20d114f569eec879a26da91dac57ac88 languageName: node linkType: hard -"@modelcontextprotocol/sdk@npm:1.27.1": - version: 1.27.1 - resolution: "@modelcontextprotocol/sdk@npm:1.27.1" +"@modelcontextprotocol/sdk@npm:1.29.0": + version: 1.29.0 + resolution: "@modelcontextprotocol/sdk@npm:1.29.0" dependencies: "@hono/node-server": "npm:^1.19.9" ajv: "npm:^8.17.1" @@ -786,7 +786,7 @@ __metadata: optional: true zod: optional: false - checksum: 10c0/1b8ad87093c9e43174c7d65864b3d826a8dd050d5c32248f5da49fd72c51b556ebd702e3e49a7f1cc7fa25717a3f7fcee22ed89edd5bd3d8f4e1f8ca499b365e + checksum: 10c0/7c4bc339205b1652330cd4e6b121cc859079655f2b9c0506bbb15563ba0d07924bda3d949705530532db7f4d2cb86d633dc8f92bc32803d97c7bece2ac63e29f languageName: node linkType: hard @@ -805,8 +805,8 @@ __metadata: "@bufbuild/protobuf": "npm:1.10.1" "@monorepo/api": "workspace:*" "@monorepo/common": "workspace:*" - "@reboot-dev/reboot": "npm:0.46.0" - "@reboot-dev/reboot-api": "npm:0.46.0" + "@reboot-dev/reboot": "npm:1.0.3" + "@reboot-dev/reboot-api": "npm:1.0.3" prosemirror-model: "npm:^1.23.0" prosemirror-transform: "npm:^1.1.0" languageName: unknown @@ -831,7 +831,7 @@ __metadata: "@monorepo/api": "workspace:*" "@monorepo/common": "workspace:*" "@nytimes/react-prosemirror": "npm:^0.6.2" - "@reboot-dev/reboot-react": "npm:0.46.0" + "@reboot-dev/reboot-react": "npm:1.0.3" "@types/node": "npm:^20" "@types/react": "npm:^18" "@types/react-dom": "npm:^18" @@ -1011,27 +1011,27 @@ __metadata: languageName: node linkType: hard -"@reboot-dev/reboot-api@npm:0.46.0": - version: 0.46.0 - resolution: "@reboot-dev/reboot-api@npm:0.46.0" +"@reboot-dev/reboot-api@npm:1.0.3": + version: 1.0.3 + resolution: "@reboot-dev/reboot-api@npm:1.0.3" dependencies: "@scarf/scarf": "npm:1.4.0" typescript: "npm:5.4.5" zod: "npm:^3.25.51" peerDependencies: "@bufbuild/protobuf": 1.10.1 - checksum: 10c0/988d942f13c68029429e5c763a65e952f4b1fa029ba3445d2c5481a986f96e424bfbcb07f1a957198763bec6779cccf64d13900c0334999f598b23c6168217e3 + checksum: 10c0/d32c0531347db78af6c2a38240a6b1112b79d0bde507f309766ddf495ce54355e58e22b068758f8e4b0933cc4ce1d1cabb26f1c7fbd4006a77e2cdac729f414f languageName: node linkType: hard -"@reboot-dev/reboot-react@npm:0.46.0": - version: 0.46.0 - resolution: "@reboot-dev/reboot-react@npm:0.46.0" +"@reboot-dev/reboot-react@npm:1.0.3": + version: 1.0.3 + resolution: "@reboot-dev/reboot-react@npm:1.0.3" dependencies: - "@modelcontextprotocol/ext-apps": "npm:1.2.0" - "@modelcontextprotocol/sdk": "npm:1.27.1" - "@reboot-dev/reboot-api": "npm:0.46.0" - "@reboot-dev/reboot-web": "npm:0.46.0" + "@modelcontextprotocol/ext-apps": "npm:1.5.0" + "@modelcontextprotocol/sdk": "npm:1.29.0" + "@reboot-dev/reboot-api": "npm:1.0.3" + "@reboot-dev/reboot-web": "npm:1.0.3" "@scarf/scarf": "npm:1.4.0" "@types/uuid": "npm:^9.0.4" js-sha1: "npm:0.7.0" @@ -1042,15 +1042,15 @@ __metadata: "@bufbuild/protobuf": 1.10.1 react: ">=18.0.0" react-dom: ">=18.0.0" - checksum: 10c0/6d832caa130c2e467e8f3430abebbe903f21128ec9cbfd55185644dd9bf914d439fe9c8d5064541b5641463432858c4da67aefb4fa3c4470e2a675603a39cb83 + checksum: 10c0/4ac8544629455e1c19d0dfa3e69baa59c9cff5ae718f9e0a5a25671b266a6844343ee546567a1fbdcf835e4782e12ce8a7679e8837d3b59ef398206850810d00 languageName: node linkType: hard -"@reboot-dev/reboot-web@npm:0.46.0": - version: 0.46.0 - resolution: "@reboot-dev/reboot-web@npm:0.46.0" +"@reboot-dev/reboot-web@npm:1.0.3": + version: 1.0.3 + resolution: "@reboot-dev/reboot-web@npm:1.0.3" dependencies: - "@reboot-dev/reboot-api": "npm:0.46.0" + "@reboot-dev/reboot-api": "npm:1.0.3" "@scarf/scarf": "npm:1.4.0" js-sha1: "npm:0.7.0" lru-cache-idb: "npm:^0.5.2" @@ -1059,17 +1059,17 @@ __metadata: uuid: "npm:11.1.0" peerDependencies: "@bufbuild/protobuf": 1.10.1 - checksum: 10c0/1e0068cb15c420a8c93b68107e18c3d0e6fb3b378a54e484029bda5b008893261919d05d9094449c3b261dd6ecd12a0d19eee6a36d4bde3c93a89795ac2afcf2 + checksum: 10c0/0a0457e9d56b7179ca4dc2ac9767cd3ef6687c0089e69817069b92c382018a3075feeb8ce25a619d44a24defc10206d70676a445291d422db6170fb37aaa7a4e languageName: node linkType: hard -"@reboot-dev/reboot@npm:0.46.0": - version: 0.46.0 - resolution: "@reboot-dev/reboot@npm:0.46.0" +"@reboot-dev/reboot@npm:1.0.3": + version: 1.0.3 + resolution: "@reboot-dev/reboot@npm:1.0.3" dependencies: "@bufbuild/protoc-gen-es": "npm:1.10.1" "@bufbuild/protoplugin": "npm:1.10.1" - "@reboot-dev/reboot-api": "npm:0.46.0" + "@reboot-dev/reboot-api": "npm:1.0.3" "@scarf/scarf": "npm:1.4.0" "@standard-schema/spec": "npm:1.0.0" chalk: "npm:^4.1.2" @@ -1090,7 +1090,7 @@ __metadata: rbt: rbt.js rbt-esbuild: rbt-esbuild.js zod-to-proto: zod-to-proto.js - checksum: 10c0/66d746c93408bc2fcf4c56b0ab508d4ebdd9fad480b5bffaa86de35b2ea90123cf2e4bba783970109555957f6b08e6985a0331eb904a477b4d8d48a1546dcbb9 + checksum: 10c0/0b0331a3175fcd2189e9b2df5d431bb3c8b694020c700523bcb3b8c8b3748a2129b849e7eed1e58da0837eb8e4ecfd585de8f5224da1e4abf1d7070200c01608 languageName: node linkType: hard diff --git a/reboot/examples/prosemirror/Dockerfile b/reboot/examples/prosemirror/Dockerfile index 4b856125..6bd5cafb 100644 --- a/reboot/examples/prosemirror/Dockerfile +++ b/reboot/examples/prosemirror/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/reboot-dev/reboot-base:0.46.0 +FROM ghcr.io/reboot-dev/reboot-base:1.0.3 WORKDIR /app diff --git a/reboot/examples/prosemirror/README.md b/reboot/examples/prosemirror/README.md index afed4af9..2bef347f 100644 --- a/reboot/examples/prosemirror/README.md +++ b/reboot/examples/prosemirror/README.md @@ -1,6 +1,6 @@ # Reboot + ProseMirror -(0) We suggest you use a GitHub Codespace (just click button below) _otherwise_ follow instructions [here](https://docs.reboot.dev/develop/define/overview) for getting your own environment set up. +(0) We suggest you use a GitHub Codespace (just click button below) _otherwise_ follow instructions [here](https://docs.reboot.dev/learn_more/define/overview) for getting your own environment set up. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/reboot-dev/reboot-prosemirror) diff --git a/reboot/examples/prosemirror/backend/.rbtrc b/reboot/examples/prosemirror/backend/.rbtrc index 354bdc73..96cf2489 100644 --- a/reboot/examples/prosemirror/backend/.rbtrc +++ b/reboot/examples/prosemirror/backend/.rbtrc @@ -32,15 +32,15 @@ generate --react=../api dev run --nodejs # Save state between chaos restarts. -dev run --name=docs +dev run --application-name=docs # Run the application! dev run --application=src/main.ts # Use the same name to expunge that we do to run. -dev expunge --name=docs +dev expunge --application-name=docs -serve run --name=docs +serve run --application-name=docs # Tell `rbt serve` that this is a Node.js application. serve run --nodejs diff --git a/reboot/examples/prosemirror/backend/package.json b/reboot/examples/prosemirror/backend/package.json index 24451fca..0cfa9ee5 100644 --- a/reboot/examples/prosemirror/backend/package.json +++ b/reboot/examples/prosemirror/backend/package.json @@ -10,8 +10,8 @@ "@bufbuild/protobuf": "1.10.1", "@monorepo/api": "workspace:*", "@monorepo/common": "workspace:*", - "@reboot-dev/reboot": "0.46.0", - "@reboot-dev/reboot-api": "0.46.0", + "@reboot-dev/reboot": "1.0.3", + "@reboot-dev/reboot-api": "1.0.3", "prosemirror-model": "^1.23.0", "prosemirror-transform": "^1.1.0" } diff --git a/reboot/examples/prosemirror/web/package.json b/reboot/examples/prosemirror/web/package.json index f3e9bab0..69400130 100644 --- a/reboot/examples/prosemirror/web/package.json +++ b/reboot/examples/prosemirror/web/package.json @@ -14,7 +14,7 @@ "@monorepo/api": "workspace:*", "@monorepo/common": "workspace:*", "@nytimes/react-prosemirror": "^0.6.2", - "@reboot-dev/reboot-react": "0.46.0", + "@reboot-dev/reboot-react": "1.0.3", "next": "15.0.5", "prosemirror-collab": "^1.3.1", "prosemirror-history": "^1.4.1", diff --git a/reboot/examples/prosemirror/yarn.lock b/reboot/examples/prosemirror/yarn.lock index 2aeb3c2a..11af7946 100644 --- a/reboot/examples/prosemirror/yarn.lock +++ b/reboot/examples/prosemirror/yarn.lock @@ -713,11 +713,11 @@ __metadata: languageName: node linkType: hard -"@modelcontextprotocol/ext-apps@npm:1.2.0": - version: 1.2.0 - resolution: "@modelcontextprotocol/ext-apps@npm:1.2.0" +"@modelcontextprotocol/ext-apps@npm:1.5.0": + version: 1.5.0 + resolution: "@modelcontextprotocol/ext-apps@npm:1.5.0" peerDependencies: - "@modelcontextprotocol/sdk": ^1.24.0 + "@modelcontextprotocol/sdk": ^1.29.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 zod: ^3.25.0 || ^4.0.0 @@ -726,13 +726,13 @@ __metadata: optional: true react-dom: optional: true - checksum: 10c0/0fa70e5e6a7218579448e4b78880ab83b9c70c6f0fd56dfddd73c8b93c6be4fc1af79266f59b4245c4aea055455517095c7055013841439345804f5ac5cb14a8 + checksum: 10c0/b8ef86ee48fa5658c152a4ad282ab144335be9d25362a475c834155495d9b2884b1c524703df8ee599d43789c4ea92de20d114f569eec879a26da91dac57ac88 languageName: node linkType: hard -"@modelcontextprotocol/sdk@npm:1.27.1": - version: 1.27.1 - resolution: "@modelcontextprotocol/sdk@npm:1.27.1" +"@modelcontextprotocol/sdk@npm:1.29.0": + version: 1.29.0 + resolution: "@modelcontextprotocol/sdk@npm:1.29.0" dependencies: "@hono/node-server": "npm:^1.19.9" ajv: "npm:^8.17.1" @@ -759,7 +759,7 @@ __metadata: optional: true zod: optional: false - checksum: 10c0/1b8ad87093c9e43174c7d65864b3d826a8dd050d5c32248f5da49fd72c51b556ebd702e3e49a7f1cc7fa25717a3f7fcee22ed89edd5bd3d8f4e1f8ca499b365e + checksum: 10c0/7c4bc339205b1652330cd4e6b121cc859079655f2b9c0506bbb15563ba0d07924bda3d949705530532db7f4d2cb86d633dc8f92bc32803d97c7bece2ac63e29f languageName: node linkType: hard @@ -776,8 +776,8 @@ __metadata: "@bufbuild/protobuf": "npm:1.10.1" "@monorepo/api": "workspace:*" "@monorepo/common": "workspace:*" - "@reboot-dev/reboot": "npm:0.46.0" - "@reboot-dev/reboot-api": "npm:0.46.0" + "@reboot-dev/reboot": "npm:1.0.3" + "@reboot-dev/reboot-api": "npm:1.0.3" prosemirror-model: "npm:^1.23.0" prosemirror-transform: "npm:^1.1.0" languageName: unknown @@ -802,7 +802,7 @@ __metadata: "@monorepo/api": "workspace:*" "@monorepo/common": "workspace:*" "@nytimes/react-prosemirror": "npm:^0.6.2" - "@reboot-dev/reboot-react": "npm:0.46.0" + "@reboot-dev/reboot-react": "npm:1.0.3" "@types/node": "npm:^20" "@types/react": "npm:^18" "@types/react-dom": "npm:^18" @@ -971,27 +971,27 @@ __metadata: languageName: node linkType: hard -"@reboot-dev/reboot-api@npm:0.46.0": - version: 0.46.0 - resolution: "@reboot-dev/reboot-api@npm:0.46.0" +"@reboot-dev/reboot-api@npm:1.0.3": + version: 1.0.3 + resolution: "@reboot-dev/reboot-api@npm:1.0.3" dependencies: "@scarf/scarf": "npm:1.4.0" typescript: "npm:5.4.5" zod: "npm:^3.25.51" peerDependencies: "@bufbuild/protobuf": 1.10.1 - checksum: 10c0/988d942f13c68029429e5c763a65e952f4b1fa029ba3445d2c5481a986f96e424bfbcb07f1a957198763bec6779cccf64d13900c0334999f598b23c6168217e3 + checksum: 10c0/d32c0531347db78af6c2a38240a6b1112b79d0bde507f309766ddf495ce54355e58e22b068758f8e4b0933cc4ce1d1cabb26f1c7fbd4006a77e2cdac729f414f languageName: node linkType: hard -"@reboot-dev/reboot-react@npm:0.46.0": - version: 0.46.0 - resolution: "@reboot-dev/reboot-react@npm:0.46.0" +"@reboot-dev/reboot-react@npm:1.0.3": + version: 1.0.3 + resolution: "@reboot-dev/reboot-react@npm:1.0.3" dependencies: - "@modelcontextprotocol/ext-apps": "npm:1.2.0" - "@modelcontextprotocol/sdk": "npm:1.27.1" - "@reboot-dev/reboot-api": "npm:0.46.0" - "@reboot-dev/reboot-web": "npm:0.46.0" + "@modelcontextprotocol/ext-apps": "npm:1.5.0" + "@modelcontextprotocol/sdk": "npm:1.29.0" + "@reboot-dev/reboot-api": "npm:1.0.3" + "@reboot-dev/reboot-web": "npm:1.0.3" "@scarf/scarf": "npm:1.4.0" "@types/uuid": "npm:^9.0.4" js-sha1: "npm:0.7.0" @@ -1002,15 +1002,15 @@ __metadata: "@bufbuild/protobuf": 1.10.1 react: ">=18.0.0" react-dom: ">=18.0.0" - checksum: 10c0/6d832caa130c2e467e8f3430abebbe903f21128ec9cbfd55185644dd9bf914d439fe9c8d5064541b5641463432858c4da67aefb4fa3c4470e2a675603a39cb83 + checksum: 10c0/4ac8544629455e1c19d0dfa3e69baa59c9cff5ae718f9e0a5a25671b266a6844343ee546567a1fbdcf835e4782e12ce8a7679e8837d3b59ef398206850810d00 languageName: node linkType: hard -"@reboot-dev/reboot-web@npm:0.46.0": - version: 0.46.0 - resolution: "@reboot-dev/reboot-web@npm:0.46.0" +"@reboot-dev/reboot-web@npm:1.0.3": + version: 1.0.3 + resolution: "@reboot-dev/reboot-web@npm:1.0.3" dependencies: - "@reboot-dev/reboot-api": "npm:0.46.0" + "@reboot-dev/reboot-api": "npm:1.0.3" "@scarf/scarf": "npm:1.4.0" js-sha1: "npm:0.7.0" lru-cache-idb: "npm:^0.5.2" @@ -1019,17 +1019,17 @@ __metadata: uuid: "npm:11.1.0" peerDependencies: "@bufbuild/protobuf": 1.10.1 - checksum: 10c0/1e0068cb15c420a8c93b68107e18c3d0e6fb3b378a54e484029bda5b008893261919d05d9094449c3b261dd6ecd12a0d19eee6a36d4bde3c93a89795ac2afcf2 + checksum: 10c0/0a0457e9d56b7179ca4dc2ac9767cd3ef6687c0089e69817069b92c382018a3075feeb8ce25a619d44a24defc10206d70676a445291d422db6170fb37aaa7a4e languageName: node linkType: hard -"@reboot-dev/reboot@npm:0.46.0": - version: 0.46.0 - resolution: "@reboot-dev/reboot@npm:0.46.0" +"@reboot-dev/reboot@npm:1.0.3": + version: 1.0.3 + resolution: "@reboot-dev/reboot@npm:1.0.3" dependencies: "@bufbuild/protoc-gen-es": "npm:1.10.1" "@bufbuild/protoplugin": "npm:1.10.1" - "@reboot-dev/reboot-api": "npm:0.46.0" + "@reboot-dev/reboot-api": "npm:1.0.3" "@scarf/scarf": "npm:1.4.0" "@standard-schema/spec": "npm:1.0.0" chalk: "npm:^4.1.2" @@ -1050,7 +1050,7 @@ __metadata: rbt: rbt.js rbt-esbuild: rbt-esbuild.js zod-to-proto: zod-to-proto.js - checksum: 10c0/66d746c93408bc2fcf4c56b0ab508d4ebdd9fad480b5bffaa86de35b2ea90123cf2e4bba783970109555957f6b08e6985a0331eb904a477b4d8d48a1546dcbb9 + checksum: 10c0/0b0331a3175fcd2189e9b2df5d431bb3c8b694020c700523bcb3b8c8b3748a2129b849e7eed1e58da0837eb8e4ecfd585de8f5224da1e4abf1d7070200c01608 languageName: node linkType: hard diff --git a/reboot/mcp/factories.py b/reboot/mcp/factories.py index 768028fd..0ff1810c 100644 --- a/reboot/mcp/factories.py +++ b/reboot/mcp/factories.py @@ -19,11 +19,21 @@ _init_session_state, _set_user_id, ) -from reboot.mcp.ui import _resource_meta +from reboot.mcp.proxy import _UI_ASSETS_PREFIX +from reboot.mcp.ui import ( + _request_user_agent, + _resolve_dist_path, + _resource_meta, + _ui_tool_cache_bust_info, + compute_ui_cache_bust, + find_project_root_from, + find_ui_asset_info, +) from starlette.requests import Request -from starlette.responses import JSONResponse +from starlette.responses import FileResponse, JSONResponse, Response from starlette.types import Receive, Scope, Send from typing import Any, Awaitable, Callable, Optional, Sequence +from urllib.parse import unquote logger = logging.getLogger(__name__) @@ -83,6 +93,147 @@ async def _read_resource_with_dynamic_meta( FastMCP.read_resource = _read_resource_with_dynamic_meta # type: ignore[method-assign] +# --------------------------------------------------------------------------- +# Patch `FastMCP.list_tools` to recompute UI cache-bust tokens on +# each call so clients that re-list tools on new sessions (Claude, +# MCPJam) pick up dist-file changes without requiring a server +# restart. +# +# Each UI tool's recompute inputs live in the server-local +# `_ui_tool_cache_bust_info` registry in `reboot.mcp.ui`, +# populated by generated `_add_mcp` code via +# `register_ui_tool_for_cache_bust`. On every `tools/list`, we +# look up each tool by name, re-hash the current dist artifact, +# and rewrite `_meta.ui.resourceUri` with the fresh token. +# ChatGPT doesn't re-list so it doesn't benefit here — but for +# clients that do, this turns the `--config=dist` dev loop from +# "edit, build, restart, test" into "edit, build, new session". +# +# The registry is intentionally kept server-local rather than +# stashed in tool `_meta`: the client never sees our internal +# bookkeeping, and correctness doesn't depend on how MCP hosts +# treat unknown `_meta` keys. +# +# Failures during recompute are swallowed (the existing URI +# stays) so `tools/list` never breaks over a cache-bust issue. +# --------------------------------------------------------------------------- + +_original_list_tools = FastMCP.list_tools + + +async def _list_tools_with_fresh_cache_busts( + self: FastMCP, +) -> list[mcp.types.Tool]: + tools = await _original_list_tools(self) + for tool in tools: + info = _ui_tool_cache_bust_info.get(tool.name) + if info is None: + continue + try: + project_root = find_project_root_from(info["caller_file"]) + token = compute_ui_cache_bust( + project_root, + info["ui_path"], + info["ui_name"], + info.get("artifact_path"), + ) + except Exception: + # Keep the existing URI baked in at registration. + # A `tools/list` response must never fail just + # because a cache-bust refresh couldn't resolve. + continue + meta = tool.meta + if not isinstance(meta, dict): + continue + ui_meta = meta.get("ui") + if isinstance(ui_meta, dict): + ui_meta["resourceUri"] = info["uri_prefix"] + token + return tools + + +FastMCP.list_tools = _list_tools_with_fresh_cache_busts # type: ignore[method-assign] + + +async def _serve_ui_asset( + scope: Scope, + receive: Receive, + send: Send, + path: str, +) -> None: + """Serve a file from a UI's dist directory. + + Called for requests matching `/ui-assets///`. + The `` segment is part of the URL identity (so + browser HTTP caches invalidate when it rotates) but is + otherwise ignored server-side — the actual bytes come from + the current filesystem. Looks up `` in the + `_ui_tool_cache_bust_info` registry to find the project + root and ui_path, resolves the dist directory via + `_resolve_dist_path`, and serves `` (defaulting to + `index.html` if empty) from inside that directory. + + Returns 404 if the UI isn't registered, the file isn't in + the dist, or the resolved path escapes the dist directory. + """ + + async def reject(reason: str) -> None: + # Reason is for the server log only; never echoed in + # the response body, since these can leak filesystem + # layout and registry contents. + logger.debug("ui-assets 404: %s (path=%s)", reason, path) + await Response(status_code=404)(scope, receive, send) + + # Parse: `<_UI_ASSETS_PREFIX>//`. + parts = path[len(_UI_ASSETS_PREFIX):].split("/", 2) + if len(parts) < 2: + await reject("bad ui-assets path") + return + ui_name = unquote(parts[0]) + # `parts[1]` is the cache-bust segment; intentionally + # unused for lookup — it exists purely for URL-identity + # cache-busting. + rest = parts[2] if len(parts) == 3 else "index.html" + + info = find_ui_asset_info(ui_name) + if info is None: + await reject(f"unknown ui: {ui_name}") + return + + try: + project_root = find_project_root_from(info["caller_file"]) + except RuntimeError: + await reject("project root missing") + return + + # Dist directory = parent of the `index.html` path that + # `_resolve_dist_path` returns. + dist_index = _resolve_dist_path( + project_root, info["ui_path"], info.get("artifact_path") + ) + dist_dir = dist_index.parent + # Defend against path traversal: the resolved file must + # live under `dist_dir`. + resolved = (dist_dir / rest).resolve() + try: + resolved.relative_to(dist_dir.resolve()) + except ValueError: + await reject("path escape") + return + + if not resolved.is_file(): + await reject(f"not found: {rest}") + return + + # Short-lived freshness: the URL path already varies by + # `cache_bust`, so immutable caching is safe. But keep it + # conservative for now (dev-mode iteration without restart + # could still matter). + response = FileResponse( + path=str(resolved), + headers={"Cache-Control": "no-cache"}, + ) + await response(scope, receive, send) + def create_mcp_factory( *, @@ -133,8 +284,32 @@ async def handle_set_logging_level( async def mcp_asgi_app( scope: Scope, receive: Receive, send: Send ) -> None: + # Static-asset branch: serve the UI's dist files + # directly from disk for production-mode MCP Apps. + # The path shape, set by `prod_loader_html`, is + # `/mcp<_UI_ASSETS_PREFIX>//`. + # Handled before any MCP-protocol logic (including + # bearer-token auth) so the iframe, which has no + # credentials, can load the bundle freely. + # + # Starlette mounts may or may not strip the mount + # prefix depending on wiring; accept both forms. + raw_path = scope.get("path", "") + idx = raw_path.find(_UI_ASSETS_PREFIX) + if idx != -1: + subpath = raw_path[idx:] + await _serve_ui_asset(scope, receive, send, subpath) + return + request = Request(scope, receive, send) + # Publish the incoming User-Agent so `ui_html()` can + # branch on host identity (ChatGPT gets the relay + # iframe; others get the inlined bundle). Set here, + # before handing off to the MCP transport, so the + # contextvar is captured into downstream tasks. + _request_user_agent.set(request.headers.get("user-agent")) + # If no tools are registered, there is nothing useful for an # MCP client to do. if not has_mcp_tools: diff --git a/reboot/mcp/proxy.py b/reboot/mcp/proxy.py index 4d68d4d6..3cbdc354 100644 --- a/reboot/mcp/proxy.py +++ b/reboot/mcp/proxy.py @@ -51,9 +51,17 @@ """ import logging +from urllib.parse import quote logger = logging.getLogger(__name__) +# URL prefix for the production UI-assets endpoint. Used by +# `prod_loader_html` to build the inner-iframe URL and by the +# MCP ASGI dispatcher in `factories.py` to recognize incoming +# requests. Concatenated against the MCP mount path (`/mcp`) +# and matched verbatim against `scope["path"]`. +_UI_ASSETS_PREFIX = "/ui-assets/" + def dev_loader_html( ui_path: str, @@ -94,12 +102,94 @@ def dev_loader_html( logger.debug(f"[mcp] Generating iframe wrapper for: {ui_url}") + return _iframe_relay(ui_url=ui_url, ui_name=ui_name, variant="Dev") + + +def prod_loader_html( + ui_name: str, + cache_bust: str, + reboot_url: str, +) -> str: + """Generate wrapper HTML for a production (built) UI. + + Same shape as `dev_loader_html`: the outer sandbox-proxy + iframe (srcdoc, null origin) nests a real iframe that loads + from a Reboot backend URL with a proper origin, so relative + imports and CSP `connect-src` work. The buffered relay + forwards postMessage traffic between the sandbox-proxy and + the inner app. + + Why this exists for production: + + ChatGPT runs `tools/list` only once per connector lifetime; + once it has cached a UI tool's `_meta.ui.resourceUri`, it + keeps using that URI forever (uninstall + reinstall is the + only refresh path). UI resource URIs are discovered at + list-tools time, so the cache-bust token embedded in the + URI was computed at first-connection time and never + rotates from ChatGPT's perspective. Hosts that re-list per + session (Claude, MCPJam) get a fresh URI each session and + don't have this problem. + + Returning the bundle inline therefore strands ChatGPT on + whatever bytes were on disk when it first connected. By + returning a thin relay whose nested iframe loads from + `/mcp{_UI_ASSETS_PREFIX}//index.html`, + freshness no longer depends on the URI changing. Every + iframe mount triggers a new GET against the server, which + serves the current dist file from disk (the `` + segment is ignored server-side), and the user sees the + latest bundle. + + Args: + ui_name: Display name from the API definition + (e.g., "show_clicker"). Used as the key for the + server-side asset lookup and for the iframe + `mcpUiTitle` query parameter. + cache_bust: Content-hash token computed by + `compute_ui_cache_bust`. Appears as a URL path + segment so browser HTTP caches also invalidate when + content changes. + reboot_url: Reboot server URL (extracted from request + headers). The inner iframe is loaded from this + origin. + + Returns: + HTML wrapper. The inner iframe loads + `{reboot_url}/mcp/ui-assets/{ui_name}/{cache_bust}/index.html`. + """ + # Include the token as a path segment so it's part of the + # cacheable URL identity; include `mcpUiTitle` as a query + # param for symmetry with dev mode. + ui_url = ( + f"{reboot_url}/mcp{_UI_ASSETS_PREFIX}{quote(ui_name, safe='')}" + f"/{quote(cache_bust, safe='')}/index.html" + f"?mcpUiTitle={quote(ui_name, safe='')}" + ) + + logger.debug(f"[mcp] Generating prod iframe wrapper for: {ui_url}") + + return _iframe_relay(ui_url=ui_url, ui_name=ui_name, variant="") + + +def _iframe_relay(ui_url: str, ui_name: str, variant: str) -> str: + """Shared HTML: an outer page with a nested iframe and + postMessage forwarding. Used by both `dev_loader_html` and + `prod_loader_html`. + + The relay queues messages from the host until the inner + iframe's `PostMessageTransport` sends its first message + (`ui/initialize`), then replays them in order. On reconnect + (new `ui/initialize`), saved `tool-input`/`tool-result` + notifications are replayed. + """ + title_suffix = f" ({variant})" if variant else "" return f''' - {ui_name.title()} (Dev) + {ui_name.title()}{title_suffix}