diff --git a/.prettierrc b/.prettierrc index 0ccdaff..dca7881 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,5 @@ { - "printWidth": 120, + "printWidth": 154, "proseWrap": "preserve", "semi": true, "singleQuote": false, diff --git a/CONVENTIONS.md b/CONVENTIONS.md index c025112..97a235d 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -58,22 +58,10 @@ Favour static types, explicit data flow, immutability, pure functions, compositi s.variant({ type: "error", message: s.string, code: s.optional(s.string) }) ]); ``` -- Embed **immutable snapshots** in event payloads when referencing mutable external data (e.g. a property's price at the time of booking). - ```ts - const schema_UserActions = s.discriminatedUnion([ - s.variant({ type: "view_property", property: schema_PropertySnapshot }), - s.variant({ type: "start_booking", property: schema_PropertySnapshot, draftId: DraftId.schema }), - s.variant({ type: "confirm_booking", draftId: DraftId.schema }) - ]); - ``` - Bundle related state into **union-driven state machines**. Don't use loose boolean flags (`isStreaming`, `isError`, `isLoading`) spread across stores. ```ts - type Stream = - | { type: "not_started" } - | { type: "streaming"; results: R[] } - | { type: "done"; results: R[] } - | { type: "error"; error: E }; + type Stream = { type: "not_started" } | { type: "streaming"; results: R[] } | { type: "done"; results: R[] } | { type: "error"; error: E }; type VoiceConnection = | { type: "disconnected" } @@ -232,28 +220,74 @@ Use `Future` instead of `Promise` for lazy, cancelable async. - `TreeSet`: `.insert()`, `.remove()`, `.union()` mutate in place. Use `TreeSet.from()` to clone first. - Use `.has()` for O(log n) membership. Don't use `.values().includes()` — that's O(n). -### MVar & BoundedBuffer — Async Coordination +### MVar — Async Coordination -- Use `MVar` for async synchronization. `put(v)` blocks if full, `take()` blocks if empty. Resolves in FIFO order. -- Use `BoundedBuffer` for backpressure queues with max capacity. `enqueue(v)` blocks if full, `dequeue()` blocks if empty. +Use `MVar` to coordinate concurrent operations through a single mutable cell. Operations are FIFO — fair across waiters, no starvation. - ```ts - const textBuffer = new BoundedBuffer(100); - const endSignal = MVar.newEmpty(); +- Construct with `MVar.new(v)` (full) or `MVar.newEmpty()` (empty). +- `put`/`take`/`read`/`modify` block; `tryPut`/`tryTake`/`tryRead` don't. +- `tryPut` returns `boolean`; `tryTake`/`tryRead` return `Maybe` — pattern match with `instanceof Just` / `instanceof Nothing`. +- Use `modify` (not external locks) to atomically transform shared state — the original value is restored if the callback rejects. +- Don't busy-loop on `tryTake` — use `take` to block. +- Don't use boolean flags or unbounded arrays for "done" state — use `MVar` to block until populated. - model.onToken((token) => { - textBuffer.enqueue(token); - }); - model.onDone(() => { - endSignal.put(null); - }); +**Completion/error latch with `MVar>`** — surface either "closed cleanly" or "closed with error" from a callback-based lifecycle. - for await (const text of iterable) { - await ttsService.synthesize(text); - } - ``` +```ts +const done: MVar> = MVar.newEmpty(); +ws.on("close", () => { + done.tryPut(Nothing()); +}); +ws.on("error", (err) => { + done.tryPut(Just(err)); +}); + +const result = await done.take(); +if (result instanceof Just) throw result.value; +``` -- Don't use unbounded arrays for streaming — memory leak risk. Don't use boolean flags for "done" state — use `MVar` to block until populated. +**End-of-stream latch with `MVar`** — convert a callback-based "done" signal into a value awaitable from an `AsyncIterable`. + +```ts +const end = MVar.newEmpty(); +const finished = end.take(); + +const iterable: AsyncIterable = { + [Symbol.asyncIterator]() { + return { + next: () => Promise.race([buffer.dequeue().then((value) => ({ done: false, value })), finished.then(() => ({ done: true, value: undefined }))]) + }; + } +}; + +model.onDone(() => { + end.put(null); +}); +``` + +**Atomic state via `modify`** — guard mutable shared state with automatic rollback on rejection. + +```ts +const counter = MVar.new(0); +const previous = await counter.modify(async (n) => [n + 1, n]); +``` + +### Queue & MQueue — Functional Queues + +- Use `Queue` for immutable persistent queues — `enqueue` returns a new `Queue`; safe to share across async boundaries without copying. +- Use `MQueue` for transient producer-consumer queues where mutation is local (e.g. waiter queues inside coordination primitives). +- `Queue.dequeue()` returns `Maybe<[T, Queue]>`; `MQueue.dequeue()` returns `Maybe` — both must be pattern-matched. +- Both have amortised O(1) enqueue/dequeue via two-list (front/back) representation. +- Don't reach for native `Array.shift()` for FIFO — it's O(n) and mutates. + +```ts +let q = Queue.fromArray([1, 2, 3]); +const r = q.dequeue(); +if (r instanceof Just) { + const [head, rest] = r.value; + q = rest; // thread the new queue +} +``` --- diff --git a/README.md b/README.md index 8ec7c36..42e24d8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # commit-tools -[![Version](https://img.shields.io/badge/version-0.2.0-blue.svg)](#) +[![Version](https://img.shields.io/badge/version-0.2.5-blue.svg)](#) Writing good commit messages _can_ have a high cognitive cost, especially when you make dozens of commits a day. That energy should be directed toward solving hard problems and shipping features, not summarizing them. @@ -113,6 +113,12 @@ After setup, you can switch AI models from your configured provider at any time: commit model ``` +This flow also lets you adjust the reasoning effort for the chosen model. If the model is already the one you want and you only need to change the effort level, run: + +```bash +commit effort +``` + ### 3. Generate a Commit Stage your changes, then run: @@ -144,16 +150,17 @@ To see all available commands at any time, run: commit --help ``` -| Command | Description | -| ------------------------ | ---------------------------------------- | -| `commit` | Generate a commit message (default) | -| `commit generate` | Generate a commit message | -| `commit setup` | Configure authentication and conventions | -| `commit login` | Alias for setup — re-authenticate | -| `commit doctor` | Check installation and environment | -| `commit model` | Select a different AI model | -| `commit --version`, `-v` | Show version | -| `commit --help`, `-h` | Show help | +| Command | Description | +| ------------------------ | ------------------------------------------------- | +| `commit` | Generate a commit message (default) | +| `commit generate` | Generate a commit message | +| `commit setup` | Configure authentication and conventions | +| `commit login` | Alias for setup — re-authenticate | +| `commit doctor` | Check installation and environment | +| `commit model` | Select a different AI model | +| `commit effort` | Adjust the reasoning effort for the current model | +| `commit --version`, `-v` | Show version | +| `commit --help`, `-h` | Show help | ## Providers diff --git a/index.ts b/index.ts index 059c2b7..9e38aed 100755 --- a/index.ts +++ b/index.ts @@ -2,6 +2,7 @@ import { Commit } from "@/cli/commit"; import { Setup } from "@/cli/setup"; import { Doctor } from "@/cli/doctor"; import { ModelCommand } from "@/cli/model"; +import { EffortCommand } from "@/cli/effort"; import { parseArgs, showHelp, showVersion } from "@/cli/parser"; import { Future } from "@/libs/future"; @@ -26,6 +27,8 @@ const main = () => { return Doctor.create().run(); case "model": return ModelCommand.create().chain((m) => m.run()); + case "effort": + return EffortCommand.create().chain((e) => e.run()); case "version": showVersion(); return Future.resolve(undefined); diff --git a/package.json b/package.json index 7ed488e..e23573e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rafaeelricco/commit-tools", - "version": "0.2.4", + "version": "0.2.5", "type": "module", "bin": { "commit": "./dist/index.js" @@ -45,9 +45,9 @@ "typescript": "^5" }, "dependencies": { - "@anthropic-ai/sdk": "^0.87.0", + "@anthropic-ai/sdk": "0.90.0", "@clack/prompts": "^1.0.1", - "@google/generative-ai": "^0.24.1", + "@google/genai": "1.50.1", "chalk": "^5.6.2", "cli-table3": "^0.6.5", "fluture": "^14.0.0", @@ -55,7 +55,7 @@ "ink": "^6.8.0", "luxon": "^3.7.2", "open": "^11.0.0", - "openai": "^6.25.0", + "openai": "6.34.0", "picocolors": "^1.1.1", "react": "^19.2.4" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 925b90d..0b6c32c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,14 +8,14 @@ importers: .: dependencies: "@anthropic-ai/sdk": - specifier: ^0.87.0 - version: 0.87.0 + specifier: 0.90.0 + version: 0.90.0 "@clack/prompts": specifier: ^1.0.1 version: 1.1.0 - "@google/generative-ai": - specifier: ^0.24.1 - version: 0.24.1 + "@google/genai": + specifier: 1.50.1 + version: 1.50.1 chalk: specifier: ^5.6.2 version: 5.6.2 @@ -38,8 +38,8 @@ importers: specifier: ^11.0.0 version: 11.0.0 openai: - specifier: ^6.25.0 - version: 6.29.0(ws@8.19.0) + specifier: 6.34.0 + version: 6.34.0(ws@8.19.0) picocolors: specifier: ^1.1.1 version: 1.1.1 @@ -92,13 +92,11 @@ importers: packages: "@alcalzone/ansi-tokenize@0.2.5": - resolution: - { integrity: sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw== } + resolution: { integrity: sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw== } engines: { node: ">=18" } - "@anthropic-ai/sdk@0.87.0": - resolution: - { integrity: sha512-ZvBWT5VkPTW6b8LIpugpuAkpcYPSLOXdWTcgQrpUqf4IeJ5ZrH5rT8sTsUDvxPCHAlRG3nF4VIWfjw6uLhJ18g== } + "@anthropic-ai/sdk@0.90.0": + resolution: { integrity: sha512-MzZtPabJF1b0FTDl6Z6H5ljphPwACLGP13lu8MTiB8jXaW/YXlpOp+Po2cVou3MPM5+f5toyLnul9whKCy7fBg== } hasBin: true peerDependencies: zod: ^3.25.0 || ^4.0.0 @@ -107,533 +105,479 @@ packages: optional: true "@babel/runtime@7.29.2": - resolution: - { integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g== } + resolution: { integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g== } engines: { node: ">=6.9.0" } "@clack/core@1.1.0": - resolution: - { integrity: sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA== } + resolution: { integrity: sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA== } "@clack/prompts@1.1.0": - resolution: - { integrity: sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g== } + resolution: { integrity: sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g== } "@colors/colors@1.5.0": - resolution: - { integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== } + resolution: { integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== } engines: { node: ">=0.1.90" } "@esbuild/aix-ppc64@0.27.4": - resolution: - { integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q== } + resolution: { integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q== } engines: { node: ">=18" } cpu: [ppc64] os: [aix] "@esbuild/android-arm64@0.27.4": - resolution: - { integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw== } + resolution: { integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw== } engines: { node: ">=18" } cpu: [arm64] os: [android] "@esbuild/android-arm@0.27.4": - resolution: - { integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ== } + resolution: { integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ== } engines: { node: ">=18" } cpu: [arm] os: [android] "@esbuild/android-x64@0.27.4": - resolution: - { integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw== } + resolution: { integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw== } engines: { node: ">=18" } cpu: [x64] os: [android] "@esbuild/darwin-arm64@0.27.4": - resolution: - { integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ== } + resolution: { integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ== } engines: { node: ">=18" } cpu: [arm64] os: [darwin] "@esbuild/darwin-x64@0.27.4": - resolution: - { integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw== } + resolution: { integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw== } engines: { node: ">=18" } cpu: [x64] os: [darwin] "@esbuild/freebsd-arm64@0.27.4": - resolution: - { integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw== } + resolution: { integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw== } engines: { node: ">=18" } cpu: [arm64] os: [freebsd] "@esbuild/freebsd-x64@0.27.4": - resolution: - { integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ== } + resolution: { integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ== } engines: { node: ">=18" } cpu: [x64] os: [freebsd] "@esbuild/linux-arm64@0.27.4": - resolution: - { integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA== } + resolution: { integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA== } engines: { node: ">=18" } cpu: [arm64] os: [linux] "@esbuild/linux-arm@0.27.4": - resolution: - { integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg== } + resolution: { integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg== } engines: { node: ">=18" } cpu: [arm] os: [linux] "@esbuild/linux-ia32@0.27.4": - resolution: - { integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA== } + resolution: { integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA== } engines: { node: ">=18" } cpu: [ia32] os: [linux] "@esbuild/linux-loong64@0.27.4": - resolution: - { integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA== } + resolution: { integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA== } engines: { node: ">=18" } cpu: [loong64] os: [linux] "@esbuild/linux-mips64el@0.27.4": - resolution: - { integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw== } + resolution: { integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw== } engines: { node: ">=18" } cpu: [mips64el] os: [linux] "@esbuild/linux-ppc64@0.27.4": - resolution: - { integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA== } + resolution: { integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA== } engines: { node: ">=18" } cpu: [ppc64] os: [linux] "@esbuild/linux-riscv64@0.27.4": - resolution: - { integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw== } + resolution: { integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw== } engines: { node: ">=18" } cpu: [riscv64] os: [linux] "@esbuild/linux-s390x@0.27.4": - resolution: - { integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA== } + resolution: { integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA== } engines: { node: ">=18" } cpu: [s390x] os: [linux] "@esbuild/linux-x64@0.27.4": - resolution: - { integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA== } + resolution: { integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA== } engines: { node: ">=18" } cpu: [x64] os: [linux] "@esbuild/netbsd-arm64@0.27.4": - resolution: - { integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q== } + resolution: { integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q== } engines: { node: ">=18" } cpu: [arm64] os: [netbsd] "@esbuild/netbsd-x64@0.27.4": - resolution: - { integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg== } + resolution: { integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg== } engines: { node: ">=18" } cpu: [x64] os: [netbsd] "@esbuild/openbsd-arm64@0.27.4": - resolution: - { integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow== } + resolution: { integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow== } engines: { node: ">=18" } cpu: [arm64] os: [openbsd] "@esbuild/openbsd-x64@0.27.4": - resolution: - { integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ== } + resolution: { integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ== } engines: { node: ">=18" } cpu: [x64] os: [openbsd] "@esbuild/openharmony-arm64@0.27.4": - resolution: - { integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg== } + resolution: { integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg== } engines: { node: ">=18" } cpu: [arm64] os: [openharmony] "@esbuild/sunos-x64@0.27.4": - resolution: - { integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g== } + resolution: { integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g== } engines: { node: ">=18" } cpu: [x64] os: [sunos] "@esbuild/win32-arm64@0.27.4": - resolution: - { integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg== } + resolution: { integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg== } engines: { node: ">=18" } cpu: [arm64] os: [win32] "@esbuild/win32-ia32@0.27.4": - resolution: - { integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw== } + resolution: { integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw== } engines: { node: ">=18" } cpu: [ia32] os: [win32] "@esbuild/win32-x64@0.27.4": - resolution: - { integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg== } + resolution: { integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg== } engines: { node: ">=18" } cpu: [x64] os: [win32] "@eslint-community/eslint-utils@4.9.1": - resolution: - { integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== } + resolution: { integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 "@eslint-community/regexpp@4.12.2": - resolution: - { integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== } + resolution: { integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== } engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } "@eslint/config-array@0.21.2": - resolution: - { integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw== } + resolution: { integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } "@eslint/config-helpers@0.4.2": - resolution: - { integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== } + resolution: { integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } "@eslint/core@0.17.0": - resolution: - { integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== } + resolution: { integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } "@eslint/eslintrc@3.3.5": - resolution: - { integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg== } + resolution: { integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } "@eslint/js@9.39.4": - resolution: - { integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw== } + resolution: { integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } "@eslint/object-schema@2.1.7": - resolution: - { integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== } + resolution: { integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } "@eslint/plugin-kit@0.4.1": - resolution: - { integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== } + resolution: { integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - "@google/generative-ai@0.24.1": - resolution: - { integrity: sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q== } - engines: { node: ">=18.0.0" } + "@google/genai@1.50.1": + resolution: { integrity: sha512-YbkX7H9+1Pt8wOt7DDREy8XSoiL6fRDzZQRyaVBarFf8MR3zHGqVdvM4cLbDXqPhxqvegZShgfxb8kw9C7YhAQ== } + engines: { node: ">=20.0.0" } + peerDependencies: + "@modelcontextprotocol/sdk": ^1.25.2 + peerDependenciesMeta: + "@modelcontextprotocol/sdk": + optional: true "@humanfs/core@0.19.2": - resolution: - { integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA== } + resolution: { integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA== } engines: { node: ">=18.18.0" } "@humanfs/node@0.16.8": - resolution: - { integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ== } + resolution: { integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ== } engines: { node: ">=18.18.0" } "@humanfs/types@0.15.0": - resolution: - { integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q== } + resolution: { integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q== } engines: { node: ">=18.18.0" } "@humanwhocodes/module-importer@1.0.1": - resolution: - { integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== } + resolution: { integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== } engines: { node: ">=12.22" } "@humanwhocodes/retry@0.4.3": - resolution: - { integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== } + resolution: { integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== } engines: { node: ">=18.18" } "@jridgewell/gen-mapping@0.3.13": - resolution: - { integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== } + resolution: { integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== } "@jridgewell/resolve-uri@3.1.2": - resolution: - { integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== } + resolution: { integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== } engines: { node: ">=6.0.0" } "@jridgewell/sourcemap-codec@1.5.5": - resolution: - { integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== } + resolution: { integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== } "@jridgewell/trace-mapping@0.3.31": - resolution: - { integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== } + resolution: { integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== } + + "@protobufjs/aspromise@1.1.2": + resolution: { integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== } + + "@protobufjs/base64@1.1.2": + resolution: { integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== } + + "@protobufjs/codegen@2.0.4": + resolution: { integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== } + + "@protobufjs/eventemitter@1.1.0": + resolution: { integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== } + + "@protobufjs/fetch@1.1.0": + resolution: { integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== } + + "@protobufjs/float@1.0.2": + resolution: { integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== } + + "@protobufjs/inquire@1.1.0": + resolution: { integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== } + + "@protobufjs/path@1.1.2": + resolution: { integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== } + + "@protobufjs/pool@1.1.0": + resolution: { integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== } + + "@protobufjs/utf8@1.1.0": + resolution: { integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== } "@rollup/rollup-android-arm-eabi@4.59.0": - resolution: - { integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg== } + resolution: { integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg== } cpu: [arm] os: [android] "@rollup/rollup-android-arm64@4.59.0": - resolution: - { integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q== } + resolution: { integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q== } cpu: [arm64] os: [android] "@rollup/rollup-darwin-arm64@4.59.0": - resolution: - { integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg== } + resolution: { integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg== } cpu: [arm64] os: [darwin] "@rollup/rollup-darwin-x64@4.59.0": - resolution: - { integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w== } + resolution: { integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w== } cpu: [x64] os: [darwin] "@rollup/rollup-freebsd-arm64@4.59.0": - resolution: - { integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA== } + resolution: { integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA== } cpu: [arm64] os: [freebsd] "@rollup/rollup-freebsd-x64@4.59.0": - resolution: - { integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg== } + resolution: { integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg== } cpu: [x64] os: [freebsd] "@rollup/rollup-linux-arm-gnueabihf@4.59.0": - resolution: - { integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw== } + resolution: { integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw== } cpu: [arm] os: [linux] libc: [glibc] "@rollup/rollup-linux-arm-musleabihf@4.59.0": - resolution: - { integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA== } + resolution: { integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA== } cpu: [arm] os: [linux] libc: [musl] "@rollup/rollup-linux-arm64-gnu@4.59.0": - resolution: - { integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA== } + resolution: { integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA== } cpu: [arm64] os: [linux] libc: [glibc] "@rollup/rollup-linux-arm64-musl@4.59.0": - resolution: - { integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA== } + resolution: { integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA== } cpu: [arm64] os: [linux] libc: [musl] "@rollup/rollup-linux-loong64-gnu@4.59.0": - resolution: - { integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg== } + resolution: { integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg== } cpu: [loong64] os: [linux] libc: [glibc] "@rollup/rollup-linux-loong64-musl@4.59.0": - resolution: - { integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q== } + resolution: { integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q== } cpu: [loong64] os: [linux] libc: [musl] "@rollup/rollup-linux-ppc64-gnu@4.59.0": - resolution: - { integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA== } + resolution: { integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA== } cpu: [ppc64] os: [linux] libc: [glibc] "@rollup/rollup-linux-ppc64-musl@4.59.0": - resolution: - { integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA== } + resolution: { integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA== } cpu: [ppc64] os: [linux] libc: [musl] "@rollup/rollup-linux-riscv64-gnu@4.59.0": - resolution: - { integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg== } + resolution: { integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg== } cpu: [riscv64] os: [linux] libc: [glibc] "@rollup/rollup-linux-riscv64-musl@4.59.0": - resolution: - { integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg== } + resolution: { integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg== } cpu: [riscv64] os: [linux] libc: [musl] "@rollup/rollup-linux-s390x-gnu@4.59.0": - resolution: - { integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w== } + resolution: { integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w== } cpu: [s390x] os: [linux] libc: [glibc] "@rollup/rollup-linux-x64-gnu@4.59.0": - resolution: - { integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg== } + resolution: { integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg== } cpu: [x64] os: [linux] libc: [glibc] "@rollup/rollup-linux-x64-musl@4.59.0": - resolution: - { integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg== } + resolution: { integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg== } cpu: [x64] os: [linux] libc: [musl] "@rollup/rollup-openbsd-x64@4.59.0": - resolution: - { integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ== } + resolution: { integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ== } cpu: [x64] os: [openbsd] "@rollup/rollup-openharmony-arm64@4.59.0": - resolution: - { integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA== } + resolution: { integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA== } cpu: [arm64] os: [openharmony] "@rollup/rollup-win32-arm64-msvc@4.59.0": - resolution: - { integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A== } + resolution: { integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A== } cpu: [arm64] os: [win32] "@rollup/rollup-win32-ia32-msvc@4.59.0": - resolution: - { integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA== } + resolution: { integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA== } cpu: [ia32] os: [win32] "@rollup/rollup-win32-x64-gnu@4.59.0": - resolution: - { integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA== } + resolution: { integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA== } cpu: [x64] os: [win32] "@rollup/rollup-win32-x64-msvc@4.59.0": - resolution: - { integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA== } + resolution: { integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA== } cpu: [x64] os: [win32] "@types/body-parser@1.19.6": - resolution: - { integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== } + resolution: { integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== } "@types/connect@3.4.38": - resolution: - { integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== } + resolution: { integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== } "@types/estree@1.0.8": - resolution: - { integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== } + resolution: { integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== } "@types/express-serve-static-core@5.1.1": - resolution: - { integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A== } + resolution: { integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A== } "@types/express@5.0.6": - resolution: - { integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== } + resolution: { integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== } "@types/http-errors@2.0.5": - resolution: - { integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== } + resolution: { integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== } "@types/ink-text-input@2.0.5": - resolution: - { integrity: sha512-dp6qIrf/VKQyiJfLSOTk7XhbAG3SkGLMrL/5QwaOpYae20+/+VFLuEgpfjpAWOITVL7uyiE9nKdQ4pk03T2F+Q== } + resolution: { integrity: sha512-dp6qIrf/VKQyiJfLSOTk7XhbAG3SkGLMrL/5QwaOpYae20+/+VFLuEgpfjpAWOITVL7uyiE9nKdQ4pk03T2F+Q== } "@types/ink@0.5.2": - resolution: - { integrity: sha512-yEuhWTRMXJkIWiaM58c5kuRot5HFccv9kjjgy0fBZG0+tYb+sMBaxHNPVqyZXspGGlmwDOQ1d3BsygWpWlW17w== } + resolution: { integrity: sha512-yEuhWTRMXJkIWiaM58c5kuRot5HFccv9kjjgy0fBZG0+tYb+sMBaxHNPVqyZXspGGlmwDOQ1d3BsygWpWlW17w== } "@types/json-schema@7.0.15": - resolution: - { integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== } + resolution: { integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== } "@types/luxon@3.7.1": - resolution: - { integrity: sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg== } + resolution: { integrity: sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg== } "@types/node@22.19.15": - resolution: - { integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg== } + resolution: { integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg== } "@types/prop-types@15.7.15": - resolution: - { integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw== } + resolution: { integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw== } "@types/qs@6.15.0": - resolution: - { integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow== } + resolution: { integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow== } "@types/range-parser@1.2.7": - resolution: - { integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== } + resolution: { integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== } "@types/react@19.2.14": - resolution: - { integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w== } + resolution: { integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w== } + + "@types/retry@0.12.0": + resolution: { integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== } "@types/send@1.2.1": - resolution: - { integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== } + resolution: { integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== } "@types/serve-static@2.2.0": - resolution: - { integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== } + resolution: { integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== } "@typescript-eslint/eslint-plugin@8.59.0": - resolution: - { integrity: sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw== } + resolution: { integrity: sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: "@typescript-eslint/parser": ^8.59.0 @@ -641,275 +585,222 @@ packages: typescript: ">=4.8.4 <6.1.0" "@typescript-eslint/parser@8.59.0": - resolution: - { integrity: sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg== } + resolution: { integrity: sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" "@typescript-eslint/project-service@8.59.0": - resolution: - { integrity: sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw== } + resolution: { integrity: sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: typescript: ">=4.8.4 <6.1.0" "@typescript-eslint/scope-manager@8.59.0": - resolution: - { integrity: sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg== } + resolution: { integrity: sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } "@typescript-eslint/tsconfig-utils@8.59.0": - resolution: - { integrity: sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg== } + resolution: { integrity: sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: typescript: ">=4.8.4 <6.1.0" "@typescript-eslint/type-utils@8.59.0": - resolution: - { integrity: sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg== } + resolution: { integrity: sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" "@typescript-eslint/types@8.59.0": - resolution: - { integrity: sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A== } + resolution: { integrity: sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } "@typescript-eslint/typescript-estree@8.59.0": - resolution: - { integrity: sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw== } + resolution: { integrity: sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: typescript: ">=4.8.4 <6.1.0" "@typescript-eslint/utils@8.59.0": - resolution: - { integrity: sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g== } + resolution: { integrity: sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" "@typescript-eslint/visitor-keys@8.59.0": - resolution: - { integrity: sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q== } + resolution: { integrity: sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } acorn-jsx@5.3.2: - resolution: - { integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== } + resolution: { integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== } peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn@8.16.0: - resolution: - { integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== } + resolution: { integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== } engines: { node: ">=0.4.0" } hasBin: true agent-base@7.1.4: - resolution: - { integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== } + resolution: { integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== } engines: { node: ">= 14" } ajv@6.14.0: - resolution: - { integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw== } + resolution: { integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw== } ansi-escapes@7.3.0: - resolution: - { integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg== } + resolution: { integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg== } engines: { node: ">=18" } ansi-regex@5.0.1: - resolution: - { integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== } + resolution: { integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== } engines: { node: ">=8" } ansi-regex@6.2.2: - resolution: - { integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== } + resolution: { integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== } engines: { node: ">=12" } ansi-styles@4.3.0: - resolution: - { integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== } + resolution: { integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== } engines: { node: ">=8" } ansi-styles@6.2.3: - resolution: - { integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== } + resolution: { integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== } engines: { node: ">=12" } any-promise@1.3.0: - resolution: - { integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== } + resolution: { integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== } argparse@2.0.1: - resolution: - { integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== } + resolution: { integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== } auto-bind@5.0.1: - resolution: - { integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg== } + resolution: { integrity: sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg== } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } balanced-match@1.0.2: - resolution: - { integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== } + resolution: { integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== } balanced-match@4.0.4: - resolution: - { integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== } + resolution: { integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== } engines: { node: 18 || 20 || >=22 } base64-js@1.5.1: - resolution: - { integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== } + resolution: { integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== } bignumber.js@9.3.1: - resolution: - { integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ== } + resolution: { integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ== } brace-expansion@1.1.14: - resolution: - { integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g== } + resolution: { integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g== } brace-expansion@5.0.5: - resolution: - { integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== } + resolution: { integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== } engines: { node: 18 || 20 || >=22 } buffer-equal-constant-time@1.0.1: - resolution: - { integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== } + resolution: { integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== } builtin-modules@3.3.0: - resolution: - { integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== } + resolution: { integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== } engines: { node: ">=6" } bundle-name@4.1.0: - resolution: - { integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== } + resolution: { integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== } engines: { node: ">=18" } bundle-require@5.1.0: - resolution: - { integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA== } + resolution: { integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA== } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } peerDependencies: esbuild: ">=0.18" bytes@3.1.2: - resolution: - { integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== } + resolution: { integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== } engines: { node: ">= 0.8" } cac@6.7.14: - resolution: - { integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== } + resolution: { integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== } engines: { node: ">=8" } callsites@3.1.0: - resolution: - { integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== } + resolution: { integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== } engines: { node: ">=6" } chalk@4.1.2: - resolution: - { integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== } + resolution: { integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== } engines: { node: ">=10" } chalk@5.6.2: - resolution: - { integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== } + resolution: { integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== } engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } chokidar@4.0.3: - resolution: - { integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== } + resolution: { integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== } engines: { node: ">= 14.16.0" } cli-boxes@3.0.0: - resolution: - { integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== } + resolution: { integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g== } engines: { node: ">=10" } cli-cursor@4.0.0: - resolution: - { integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg== } + resolution: { integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg== } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } cli-table3@0.6.5: - resolution: - { integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== } + resolution: { integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== } engines: { node: 10.* || >= 12.* } cli-truncate@5.2.0: - resolution: - { integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw== } + resolution: { integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw== } engines: { node: ">=20" } code-excerpt@4.0.0: - resolution: - { integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA== } + resolution: { integrity: sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA== } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } color-convert@2.0.1: - resolution: - { integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== } + resolution: { integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== } engines: { node: ">=7.0.0" } color-name@1.1.4: - resolution: - { integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== } + resolution: { integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== } commander@4.1.1: - resolution: - { integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== } + resolution: { integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== } engines: { node: ">= 6" } concat-map@0.0.1: - resolution: - { integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== } + resolution: { integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== } confbox@0.1.8: - resolution: - { integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== } + resolution: { integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== } consola@3.4.2: - resolution: - { integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA== } + resolution: { integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA== } engines: { node: ^14.18.0 || >=16.10.0 } convert-to-spaces@2.0.1: - resolution: - { integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ== } + resolution: { integrity: sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ== } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } cross-spawn@7.0.6: - resolution: - { integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== } + resolution: { integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== } engines: { node: ">= 8" } csstype@3.2.3: - resolution: - { integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== } + resolution: { integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== } data-uri-to-buffer@4.0.1: - resolution: - { integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== } + resolution: { integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== } engines: { node: ">= 12" } debug@4.4.3: - resolution: - { integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== } + resolution: { integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== } engines: { node: ">=6.0" } peerDependencies: supports-color: "*" @@ -918,90 +809,72 @@ packages: optional: true deep-is@0.1.4: - resolution: - { integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== } + resolution: { integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== } default-browser-id@5.0.1: - resolution: - { integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q== } + resolution: { integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q== } engines: { node: ">=18" } default-browser@5.5.0: - resolution: - { integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw== } + resolution: { integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw== } engines: { node: ">=18" } define-lazy-prop@3.0.0: - resolution: - { integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== } + resolution: { integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== } engines: { node: ">=12" } ecdsa-sig-formatter@1.0.11: - resolution: - { integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== } + resolution: { integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== } emoji-regex@10.6.0: - resolution: - { integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A== } + resolution: { integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A== } emoji-regex@8.0.0: - resolution: - { integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== } + resolution: { integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== } environment@1.1.0: - resolution: - { integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== } + resolution: { integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== } engines: { node: ">=18" } es-toolkit@1.45.1: - resolution: - { integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw== } + resolution: { integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw== } esbuild@0.27.4: - resolution: - { integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ== } + resolution: { integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ== } engines: { node: ">=18" } hasBin: true escape-string-regexp@2.0.0: - resolution: - { integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== } + resolution: { integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== } engines: { node: ">=8" } escape-string-regexp@4.0.0: - resolution: - { integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== } + resolution: { integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== } engines: { node: ">=10" } eslint-plugin-sonarjs@4.0.3: - resolution: - { integrity: sha512-5drkJKLC9qQddIiaATV0e8+ygbUc7b0Ti6VB7M2d3jmKNh3X0RaiIJYTs3dr9xnlhlrxo+/s1FoO3Jgv6O/c7g== } + resolution: { integrity: sha512-5drkJKLC9qQddIiaATV0e8+ygbUc7b0Ti6VB7M2d3jmKNh3X0RaiIJYTs3dr9xnlhlrxo+/s1FoO3Jgv6O/c7g== } peerDependencies: eslint: ^8.0.0 || ^9.0.0 || ^10.0.0 eslint-scope@8.4.0: - resolution: - { integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== } + resolution: { integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } eslint-visitor-keys@3.4.3: - resolution: - { integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== } + resolution: { integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== } engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } eslint-visitor-keys@4.2.1: - resolution: - { integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== } + resolution: { integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } eslint-visitor-keys@5.0.1: - resolution: - { integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== } + resolution: { integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== } engines: { node: ^20.19.0 || ^22.13.0 || >=24 } eslint@9.39.4: - resolution: - { integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ== } + resolution: { integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } hasBin: true peerDependencies: @@ -1011,49 +884,39 @@ packages: optional: true espree@10.4.0: - resolution: - { integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== } + resolution: { integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } esquery@1.7.0: - resolution: - { integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== } + resolution: { integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== } engines: { node: ">=0.10" } esrecurse@4.3.0: - resolution: - { integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== } + resolution: { integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== } engines: { node: ">=4.0" } estraverse@5.3.0: - resolution: - { integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== } + resolution: { integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== } engines: { node: ">=4.0" } esutils@2.0.3: - resolution: - { integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== } + resolution: { integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== } engines: { node: ">=0.10.0" } extend@3.0.2: - resolution: - { integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== } + resolution: { integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== } fast-deep-equal@3.1.3: - resolution: - { integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== } + resolution: { integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== } fast-json-stable-stringify@2.1.0: - resolution: - { integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== } + resolution: { integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== } fast-levenshtein@2.0.6: - resolution: - { integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== } + resolution: { integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== } fdir@6.5.0: - resolution: - { integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== } + resolution: { integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== } engines: { node: ">=12.0.0" } peerDependencies: picomatch: ^3 || ^4 @@ -1062,135 +925,108 @@ packages: optional: true fetch-blob@3.2.0: - resolution: - { integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== } + resolution: { integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== } engines: { node: ^12.20 || >= 14.13 } file-entry-cache@8.0.0: - resolution: - { integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== } + resolution: { integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== } engines: { node: ">=16.0.0" } find-up@5.0.0: - resolution: - { integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== } + resolution: { integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== } engines: { node: ">=10" } fix-dts-default-cjs-exports@1.0.1: - resolution: - { integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg== } + resolution: { integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg== } flat-cache@4.0.1: - resolution: - { integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== } + resolution: { integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== } engines: { node: ">=16" } flatted@3.4.2: - resolution: - { integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== } + resolution: { integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== } fluture@14.0.0: - resolution: - { integrity: sha512-pENtLF948a8DfduVKugT8edTAbFi4rBS94xjHwzLanQqIu5PYtLGl+xqs6H8TaIRL7z/B0cDpswdINzH/HRUGA== } + resolution: { integrity: sha512-pENtLF948a8DfduVKugT8edTAbFi4rBS94xjHwzLanQqIu5PYtLGl+xqs6H8TaIRL7z/B0cDpswdINzH/HRUGA== } engines: { node: ">=4.0.0" } formdata-polyfill@4.0.10: - resolution: - { integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== } + resolution: { integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== } engines: { node: ">=12.20.0" } fsevents@2.3.3: - resolution: - { integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== } + resolution: { integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== } engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } os: [darwin] functional-red-black-tree@1.0.1: - resolution: - { integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== } + resolution: { integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== } gaxios@7.1.4: - resolution: - { integrity: sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA== } + resolution: { integrity: sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA== } engines: { node: ">=18" } gcp-metadata@8.1.2: - resolution: - { integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg== } + resolution: { integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg== } engines: { node: ">=18" } get-east-asian-width@1.5.0: - resolution: - { integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA== } + resolution: { integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA== } engines: { node: ">=18" } get-tsconfig@4.13.6: - resolution: - { integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw== } + resolution: { integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw== } glob-parent@6.0.2: - resolution: - { integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== } + resolution: { integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== } engines: { node: ">=10.13.0" } globals@14.0.0: - resolution: - { integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== } + resolution: { integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== } engines: { node: ">=18" } globals@17.5.0: - resolution: - { integrity: sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g== } + resolution: { integrity: sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g== } engines: { node: ">=18" } google-auth-library@10.6.2: - resolution: - { integrity: sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw== } + resolution: { integrity: sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw== } engines: { node: ">=18" } google-logging-utils@1.1.3: - resolution: - { integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA== } + resolution: { integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA== } engines: { node: ">=14" } has-flag@4.0.0: - resolution: - { integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== } + resolution: { integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== } engines: { node: ">=8" } https-proxy-agent@7.0.6: - resolution: - { integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== } + resolution: { integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== } engines: { node: ">= 14" } ignore@5.3.2: - resolution: - { integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== } + resolution: { integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== } engines: { node: ">= 4" } ignore@7.0.5: - resolution: - { integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== } + resolution: { integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== } engines: { node: ">= 4" } import-fresh@3.3.1: - resolution: - { integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== } + resolution: { integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== } engines: { node: ">=6" } imurmurhash@0.1.4: - resolution: - { integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== } + resolution: { integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== } engines: { node: ">=0.8.19" } indent-string@5.0.0: - resolution: - { integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg== } + resolution: { integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg== } engines: { node: ">=12" } ink@6.8.0: - resolution: - { integrity: sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA== } + resolution: { integrity: sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA== } engines: { node: ">=20" } peerDependencies: "@types/react": ">=19.0.0" @@ -1203,201 +1039,162 @@ packages: optional: true is-docker@3.0.0: - resolution: - { integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== } + resolution: { integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } hasBin: true is-extglob@2.1.1: - resolution: - { integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== } + resolution: { integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== } engines: { node: ">=0.10.0" } is-fullwidth-code-point@3.0.0: - resolution: - { integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== } + resolution: { integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== } engines: { node: ">=8" } is-fullwidth-code-point@5.1.0: - resolution: - { integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ== } + resolution: { integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ== } engines: { node: ">=18" } is-glob@4.0.3: - resolution: - { integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== } + resolution: { integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== } engines: { node: ">=0.10.0" } is-in-ci@2.0.0: - resolution: - { integrity: sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w== } + resolution: { integrity: sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w== } engines: { node: ">=20" } hasBin: true is-in-ssh@1.0.0: - resolution: - { integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw== } + resolution: { integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw== } engines: { node: ">=20" } is-inside-container@1.0.0: - resolution: - { integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== } + resolution: { integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== } engines: { node: ">=14.16" } hasBin: true is-wsl@3.1.1: - resolution: - { integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw== } + resolution: { integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw== } engines: { node: ">=16" } isexe@2.0.0: - resolution: - { integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== } + resolution: { integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== } joycon@3.1.1: - resolution: - { integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== } + resolution: { integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== } engines: { node: ">=10" } js-yaml@4.1.1: - resolution: - { integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== } + resolution: { integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== } hasBin: true json-bigint@1.0.0: - resolution: - { integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== } + resolution: { integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== } json-buffer@3.0.1: - resolution: - { integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== } + resolution: { integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== } json-schema-to-ts@3.1.1: - resolution: - { integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g== } + resolution: { integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g== } engines: { node: ">=16" } json-schema-traverse@0.4.1: - resolution: - { integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== } + resolution: { integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== } json-stable-stringify-without-jsonify@1.0.1: - resolution: - { integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== } + resolution: { integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== } jsx-ast-utils-x@0.1.0: - resolution: - { integrity: sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw== } + resolution: { integrity: sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } jwa@2.0.1: - resolution: - { integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg== } + resolution: { integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg== } jws@4.0.1: - resolution: - { integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA== } + resolution: { integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA== } keyv@4.5.4: - resolution: - { integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== } + resolution: { integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== } levn@0.4.1: - resolution: - { integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== } + resolution: { integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== } engines: { node: ">= 0.8.0" } lilconfig@3.1.3: - resolution: - { integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== } + resolution: { integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== } engines: { node: ">=14" } lines-and-columns@1.2.4: - resolution: - { integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== } + resolution: { integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== } load-tsconfig@0.2.5: - resolution: - { integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg== } + resolution: { integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg== } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } locate-path@6.0.0: - resolution: - { integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== } + resolution: { integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== } engines: { node: ">=10" } lodash.merge@4.6.2: - resolution: - { integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== } + resolution: { integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== } + + long@5.3.2: + resolution: { integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA== } luxon@3.7.2: - resolution: - { integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew== } + resolution: { integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew== } engines: { node: ">=12" } magic-string@0.30.21: - resolution: - { integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== } + resolution: { integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== } mimic-fn@2.1.0: - resolution: - { integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== } + resolution: { integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== } engines: { node: ">=6" } minimatch@10.2.5: - resolution: - { integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== } + resolution: { integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== } engines: { node: 18 || 20 || >=22 } minimatch@3.1.5: - resolution: - { integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== } + resolution: { integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== } mlly@1.8.1: - resolution: - { integrity: sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ== } + resolution: { integrity: sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ== } ms@2.1.3: - resolution: - { integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== } + resolution: { integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== } mz@2.7.0: - resolution: - { integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== } + resolution: { integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== } natural-compare@1.4.0: - resolution: - { integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== } + resolution: { integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== } node-domexception@1.0.0: - resolution: - { integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== } + resolution: { integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== } engines: { node: ">=10.5.0" } deprecated: Use your platform's native DOMException instead node-fetch@3.3.2: - resolution: - { integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== } + resolution: { integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } object-assign@4.1.1: - resolution: - { integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== } + resolution: { integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== } engines: { node: ">=0.10.0" } onetime@5.1.2: - resolution: - { integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== } + resolution: { integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== } engines: { node: ">=6" } open@11.0.0: - resolution: - { integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw== } + resolution: { integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw== } engines: { node: ">=20" } - openai@6.29.0: - resolution: - { integrity: sha512-YxoArl2BItucdO89/sN6edksV0x47WUTgkgVfCgX7EuEMhbirENsgYe5oO4LTjBL9PtdKtk2WqND1gSLcTd2yw== } + openai@6.34.0: + resolution: { integrity: sha512-yEr2jdGf4tVFYG6ohmr3pF6VJuveP0EA/sS8TBx+4Eq5NT10alu5zg2dmxMXMgqpihRDQlFGpRt2XwsGj+Fyxw== } hasBin: true peerDependencies: ws: ^8.18.0 @@ -1409,65 +1206,56 @@ packages: optional: true optionator@0.9.4: - resolution: - { integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== } + resolution: { integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== } engines: { node: ">= 0.8.0" } p-limit@3.1.0: - resolution: - { integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== } + resolution: { integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== } engines: { node: ">=10" } p-locate@5.0.0: - resolution: - { integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== } + resolution: { integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== } engines: { node: ">=10" } + p-retry@4.6.2: + resolution: { integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== } + engines: { node: ">=8" } + parent-module@1.0.1: - resolution: - { integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== } + resolution: { integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== } engines: { node: ">=6" } patch-console@2.0.0: - resolution: - { integrity: sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA== } + resolution: { integrity: sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA== } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } path-exists@4.0.0: - resolution: - { integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== } + resolution: { integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== } engines: { node: ">=8" } path-key@3.1.1: - resolution: - { integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== } + resolution: { integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== } engines: { node: ">=8" } pathe@2.0.3: - resolution: - { integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== } + resolution: { integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== } picocolors@1.1.1: - resolution: - { integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== } + resolution: { integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== } picomatch@4.0.3: - resolution: - { integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== } + resolution: { integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== } engines: { node: ">=12" } pirates@4.0.7: - resolution: - { integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== } + resolution: { integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA== } engines: { node: ">= 6" } pkg-types@1.3.1: - resolution: - { integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ== } + resolution: { integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ== } postcss-load-config@6.0.1: - resolution: - { integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g== } + resolution: { integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g== } engines: { node: ">= 18" } peerDependencies: jiti: ">=1.21.0" @@ -1485,18 +1273,15 @@ packages: optional: true powershell-utils@0.1.0: - resolution: - { integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A== } + resolution: { integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A== } engines: { node: ">=20" } prelude-ls@1.2.1: - resolution: - { integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== } + resolution: { integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== } engines: { node: ">= 0.8.0" } prettier-plugin-tailwindcss@0.7.2: - resolution: - { integrity: sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA== } + resolution: { integrity: sha512-LkphyK3Fw+q2HdMOoiEHWf93fNtYJwfamoKPl7UwtjFQdei/iIBoX11G6j706FzN3ymX9mPVi97qIY8328vdnA== } engines: { node: ">=20.19" } peerDependencies: "@ianvs/prettier-plugin-sort-imports": "*" @@ -1551,234 +1336,195 @@ packages: optional: true prettier@3.8.1: - resolution: - { integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== } + resolution: { integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== } engines: { node: ">=14" } hasBin: true + protobufjs@7.5.5: + resolution: { integrity: sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg== } + engines: { node: ">=12.0.0" } + punycode@2.3.1: - resolution: - { integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== } + resolution: { integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== } engines: { node: ">=6" } react-devtools-core@7.0.1: - resolution: - { integrity: sha512-C3yNvRHaizlpiASzy7b9vbnBGLrhvdhl1CbdU6EnZgxPNbai60szdLtl+VL76UNOt5bOoVTOz5rNWZxgGt+Gsw== } + resolution: { integrity: sha512-C3yNvRHaizlpiASzy7b9vbnBGLrhvdhl1CbdU6EnZgxPNbai60szdLtl+VL76UNOt5bOoVTOz5rNWZxgGt+Gsw== } react-reconciler@0.33.0: - resolution: - { integrity: sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA== } + resolution: { integrity: sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA== } engines: { node: ">=0.10.0" } peerDependencies: react: ^19.2.0 react@19.2.4: - resolution: - { integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ== } + resolution: { integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ== } engines: { node: ">=0.10.0" } readdirp@4.1.2: - resolution: - { integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== } + resolution: { integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== } engines: { node: ">= 14.18.0" } refa@0.12.1: - resolution: - { integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g== } + resolution: { integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g== } engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } regexp-ast-analysis@0.7.1: - resolution: - { integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A== } + resolution: { integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A== } engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } resolve-from@4.0.0: - resolution: - { integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== } + resolution: { integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== } engines: { node: ">=4" } resolve-from@5.0.0: - resolution: - { integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== } + resolution: { integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== } engines: { node: ">=8" } resolve-pkg-maps@1.0.0: - resolution: - { integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== } + resolution: { integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== } restore-cursor@4.0.0: - resolution: - { integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg== } + resolution: { integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg== } engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + retry@0.13.1: + resolution: { integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== } + engines: { node: ">= 4" } + rollup@4.59.0: - resolution: - { integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg== } + resolution: { integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg== } engines: { node: ">=18.0.0", npm: ">=8.0.0" } hasBin: true run-applescript@7.1.0: - resolution: - { integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q== } + resolution: { integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q== } engines: { node: ">=18" } safe-buffer@5.2.1: - resolution: - { integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== } + resolution: { integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== } sanctuary-show@2.0.0: - resolution: - { integrity: sha512-REj4ZiioUXnDLj6EpJ9HcYDIEGaEexmB9Fg5o6InZR9f0x5PfnnC21QeU9SZ9E7G8zXSZPNjy8VRUK4safbesw== } + resolution: { integrity: sha512-REj4ZiioUXnDLj6EpJ9HcYDIEGaEexmB9Fg5o6InZR9f0x5PfnnC21QeU9SZ9E7G8zXSZPNjy8VRUK4safbesw== } sanctuary-type-identifiers@3.0.0: - resolution: - { integrity: sha512-YFXYcG0Ura1dSPd/1xLYtE2XAWUEsBHhMTZvYBOvwT8MeFQwdUOCMm2DC+r94z6H93FVq0qxDac8/D7QpJj6Mg== } + resolution: { integrity: sha512-YFXYcG0Ura1dSPd/1xLYtE2XAWUEsBHhMTZvYBOvwT8MeFQwdUOCMm2DC+r94z6H93FVq0qxDac8/D7QpJj6Mg== } scheduler@0.27.0: - resolution: - { integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== } + resolution: { integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== } scslre@0.3.0: - resolution: - { integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ== } + resolution: { integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ== } engines: { node: ^14.0.0 || >=16.0.0 } semver@7.7.4: - resolution: - { integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== } + resolution: { integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== } engines: { node: ">=10" } hasBin: true shebang-command@2.0.0: - resolution: - { integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== } + resolution: { integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== } engines: { node: ">=8" } shebang-regex@3.0.0: - resolution: - { integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== } + resolution: { integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== } engines: { node: ">=8" } shell-quote@1.8.3: - resolution: - { integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== } + resolution: { integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== } engines: { node: ">= 0.4" } signal-exit@3.0.7: - resolution: - { integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== } + resolution: { integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== } sisteransi@1.0.5: - resolution: - { integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== } + resolution: { integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== } slice-ansi@8.0.0: - resolution: - { integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg== } + resolution: { integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg== } engines: { node: ">=20" } source-map@0.7.6: - resolution: - { integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ== } + resolution: { integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ== } engines: { node: ">= 12" } stack-utils@2.0.6: - resolution: - { integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== } + resolution: { integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== } engines: { node: ">=10" } string-width@4.2.3: - resolution: - { integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== } + resolution: { integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== } engines: { node: ">=8" } string-width@7.2.0: - resolution: - { integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== } + resolution: { integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== } engines: { node: ">=18" } string-width@8.2.0: - resolution: - { integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw== } + resolution: { integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw== } engines: { node: ">=20" } strip-ansi@6.0.1: - resolution: - { integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== } + resolution: { integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== } engines: { node: ">=8" } strip-ansi@7.2.0: - resolution: - { integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w== } + resolution: { integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w== } engines: { node: ">=12" } strip-json-comments@3.1.1: - resolution: - { integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== } + resolution: { integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== } engines: { node: ">=8" } sucrase@3.35.1: - resolution: - { integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw== } + resolution: { integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw== } engines: { node: ">=16 || 14 >=14.17" } hasBin: true supports-color@7.2.0: - resolution: - { integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== } + resolution: { integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== } engines: { node: ">=8" } tagged-tag@1.0.0: - resolution: - { integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng== } + resolution: { integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng== } engines: { node: ">=20" } terminal-size@4.0.1: - resolution: - { integrity: sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ== } + resolution: { integrity: sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ== } engines: { node: ">=18" } thenify-all@1.6.0: - resolution: - { integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== } + resolution: { integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== } engines: { node: ">=0.8" } thenify@3.3.1: - resolution: - { integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== } + resolution: { integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== } tinyexec@0.3.2: - resolution: - { integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== } + resolution: { integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== } tinyglobby@0.2.15: - resolution: - { integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== } + resolution: { integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== } engines: { node: ">=12.0.0" } tree-kill@1.2.2: - resolution: - { integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== } + resolution: { integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== } hasBin: true ts-algebra@2.0.0: - resolution: - { integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw== } + resolution: { integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw== } ts-api-utils@2.5.0: - resolution: - { integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== } + resolution: { integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== } engines: { node: ">=18.12" } peerDependencies: typescript: ">=4.8.4" ts-interface-checker@0.1.13: - resolution: - { integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== } + resolution: { integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== } tsup@8.5.1: - resolution: - { integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing== } + resolution: { integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing== } engines: { node: ">=18" } hasBin: true peerDependencies: @@ -1797,76 +1543,62 @@ packages: optional: true tsx@4.21.0: - resolution: - { integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw== } + resolution: { integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw== } engines: { node: ">=18.0.0" } hasBin: true type-check@0.4.0: - resolution: - { integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== } + resolution: { integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== } engines: { node: ">= 0.8.0" } type-fest@5.4.4: - resolution: - { integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw== } + resolution: { integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw== } engines: { node: ">=20" } typescript-eslint@8.59.0: - resolution: - { integrity: sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw== } + resolution: { integrity: sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw== } engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" typescript@5.9.3: - resolution: - { integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== } + resolution: { integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== } engines: { node: ">=14.17" } hasBin: true ufo@1.6.3: - resolution: - { integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q== } + resolution: { integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q== } undici-types@6.21.0: - resolution: - { integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== } + resolution: { integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== } uri-js@4.4.1: - resolution: - { integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== } + resolution: { integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== } web-streams-polyfill@3.3.3: - resolution: - { integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== } + resolution: { integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== } engines: { node: ">= 8" } which@2.0.2: - resolution: - { integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== } + resolution: { integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== } engines: { node: ">= 8" } hasBin: true widest-line@6.0.0: - resolution: - { integrity: sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA== } + resolution: { integrity: sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA== } engines: { node: ">=20" } word-wrap@1.2.5: - resolution: - { integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== } + resolution: { integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== } engines: { node: ">=0.10.0" } wrap-ansi@9.0.2: - resolution: - { integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww== } + resolution: { integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww== } engines: { node: ">=18" } ws@7.5.10: - resolution: - { integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== } + resolution: { integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== } engines: { node: ">=8.3.0" } peerDependencies: bufferutil: ^4.0.1 @@ -1878,8 +1610,7 @@ packages: optional: true ws@8.19.0: - resolution: - { integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== } + resolution: { integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== } engines: { node: ">=10.0.0" } peerDependencies: bufferutil: ^4.0.1 @@ -1891,18 +1622,15 @@ packages: optional: true wsl-utils@0.3.1: - resolution: - { integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg== } + resolution: { integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg== } engines: { node: ">=20" } yocto-queue@0.1.0: - resolution: - { integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== } + resolution: { integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== } engines: { node: ">=10" } yoga-layout@3.2.1: - resolution: - { integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ== } + resolution: { integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ== } snapshots: "@alcalzone/ansi-tokenize@0.2.5": @@ -1910,7 +1638,7 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 - "@anthropic-ai/sdk@0.87.0": + "@anthropic-ai/sdk@0.90.0": dependencies: json-schema-to-ts: 3.1.1 @@ -2052,7 +1780,16 @@ snapshots: "@eslint/core": 0.17.0 levn: 0.4.1 - "@google/generative-ai@0.24.1": {} + "@google/genai@1.50.1": + dependencies: + google-auth-library: 10.6.2 + p-retry: 4.6.2 + protobufjs: 7.5.5 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate "@humanfs/core@0.19.2": dependencies: @@ -2084,6 +1821,29 @@ snapshots: "@jridgewell/resolve-uri": 3.1.2 "@jridgewell/sourcemap-codec": 1.5.5 + "@protobufjs/aspromise@1.1.2": {} + + "@protobufjs/base64@1.1.2": {} + + "@protobufjs/codegen@2.0.4": {} + + "@protobufjs/eventemitter@1.1.0": {} + + "@protobufjs/fetch@1.1.0": + dependencies: + "@protobufjs/aspromise": 1.1.2 + "@protobufjs/inquire": 1.1.0 + + "@protobufjs/float@1.0.2": {} + + "@protobufjs/inquire@1.1.0": {} + + "@protobufjs/path@1.1.2": {} + + "@protobufjs/pool@1.1.0": {} + + "@protobufjs/utf8@1.1.0": {} + "@rollup/rollup-android-arm-eabi@4.59.0": optional: true @@ -2212,6 +1972,8 @@ snapshots: dependencies: csstype: 3.2.3 + "@types/retry@0.12.0": {} + "@types/send@1.2.1": dependencies: "@types/node": 22.19.15 @@ -2821,6 +2583,8 @@ snapshots: lodash.merge@4.6.2: {} + long@5.3.2: {} + luxon@3.7.2: {} magic-string@0.30.21: @@ -2877,7 +2641,7 @@ snapshots: powershell-utils: 0.1.0 wsl-utils: 0.3.1 - openai@6.29.0(ws@8.19.0): + openai@6.34.0(ws@8.19.0): optionalDependencies: ws: 8.19.0 @@ -2898,6 +2662,11 @@ snapshots: dependencies: p-limit: 3.1.0 + p-retry@4.6.2: + dependencies: + "@types/retry": 0.12.0 + retry: 0.13.1 + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -2938,6 +2707,21 @@ snapshots: prettier@3.8.1: {} + protobufjs@7.5.5: + dependencies: + "@protobufjs/aspromise": 1.1.2 + "@protobufjs/base64": 1.1.2 + "@protobufjs/codegen": 2.0.4 + "@protobufjs/eventemitter": 1.1.0 + "@protobufjs/fetch": 1.1.0 + "@protobufjs/float": 1.0.2 + "@protobufjs/inquire": 1.1.0 + "@protobufjs/path": 1.1.2 + "@protobufjs/pool": 1.1.0 + "@protobufjs/utf8": 1.1.0 + "@types/node": 22.19.15 + long: 5.3.2 + punycode@2.3.1: {} react-devtools-core@7.0.1: @@ -2977,6 +2761,8 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 + retry@0.13.1: {} + rollup@4.59.0: dependencies: "@types/estree": 1.0.8 diff --git a/src/cli/commit.ts b/src/cli/commit.ts index 15e842a..6cdbdca 100644 --- a/src/cli/commit.ts +++ b/src/cli/commit.ts @@ -45,11 +45,7 @@ class Commit { return repo .checkIsGitRepo() .chain(() => this.diff()) - .chain((diff) => - this.generate(diff, this.config.commit_convention, this.config.custom_template).chain((message) => - this.interact(diff, message) - ) - ) + .chain((diff) => this.generate(diff, this.config.commit_convention, this.config.custom_template).chain((message) => this.interact(diff, message))) .mapRej((e) => { if (e instanceof Error) { p.log.error(color.red(e.message)); @@ -63,11 +59,7 @@ class Commit { } generate(diff: string, convention: CommitConvention, template: Maybe = Nothing()): Future { - return loading( - "Generating commit message...", - "Message generated!", - generateCommitMessage(this.providerConfig, diff, convention, template) - ); + return loading("Generating commit message...", "Message generated!", generateCommitMessage(this.providerConfig, diff, convention, template)); } refine(message: string, adjustment: string, diff: string): Future { @@ -93,21 +85,19 @@ class Commit { Future.concurrently< Error, { - commit: repo.CommitMetadata; - localBranch: string; - upstream: Maybe; - remoteUrl: string; + commit: Maybe; + localBranch: Maybe; + baseBranch: Maybe; + remoteUrl: Maybe; pr: pr.PrLookup; } >({ - commit: repo.getCommitMetadata(), - localBranch: repo.getCurrentBranch(), - upstream: repo.getUpstream(), - remoteUrl: repo.getTrackingRemoteUrl(), + commit: repo.findCommitMetadata(), + localBranch: repo.findCurrentBranch(), + baseBranch: repo.findBaseBranch(), + remoteUrl: repo.findTrackingRemoteUrl(), pr: pr.getOpenPullRequest() - }) - .map((parts) => renderPushNote({ ...parts, range: result.range })) - .chainRej(() => Future.resolve(undefined)) + }).map((parts) => renderPushNote({ ...parts, range: result.range })) ); } @@ -119,9 +109,7 @@ class Commit { case "commit_push": return this.handleCommitAndPush(message); case "regenerate": - return this.generate(diff, this.config.commit_convention, this.config.custom_template).chain((msg) => - this.interact(diff, msg) - ); + return this.generate(diff, this.config.commit_convention, this.config.custom_template).chain((msg) => this.interact(diff, msg)); case "adjust": return this.handleAdjust(diff, message); case "cancel": diff --git a/src/cli/doctor.ts b/src/cli/doctor.ts index 60b9ea3..6b853a3 100644 --- a/src/cli/doctor.ts +++ b/src/cli/doctor.ts @@ -1,8 +1,13 @@ export { Doctor }; +import * as pr from "@/infra/github/pr"; +import * as repo from "@/infra/git/repo"; + import { Future } from "@/libs/future"; import { CONFIG_FILE, loadConfig } from "@/infra/storage/config"; import { type AuthMethod, type ProviderConfig } from "@/domain/config/config"; +import { Just, type Maybe } from "@/libs/maybe"; +import { absurd } from "@/libs/types"; import { access } from "node:fs/promises"; import { environment } from "@/infra/env"; @@ -19,11 +24,14 @@ class Doctor { } run(): Future { + const start = performance.now(); return this.checkOAuthCredentials().chain((oauthRow) => - this.checkConfig().map((configRows) => { - const rows: CheckRow[] = [this.checkRuntime(), this.checkPlatform(), oauthRow, ...configRows]; - this.renderTable(rows); - }) + this.checkConfig().chain((configRows) => + this.checkGitContext().map((gitRows) => { + const rows: CheckRow[] = [this.checkRuntime(), this.checkPlatform(), oauthRow, ...configRows, ...gitRows]; + this.renderTable(rows, performance.now() - start); + }) + ) ); } @@ -67,7 +75,7 @@ class Doctor { const rows: CheckRow[] = [row]; const ai = config.ai; - rows.push(["Provider", color.green(ai.provider), `Model: ${ai.model}`]); + rows.push(["Provider", color.green(ai.provider), renderModelInfo(ai)]); const authMethod = ai.auth_method.type; @@ -92,7 +100,22 @@ class Doctor { }); } - private renderTable(rows: CheckRow[]): void { + private checkGitContext(): Future { + return repo + .checkIsGitRepo() + .chain((): Future => this.collectGitRows()) + .chainRej((): Future => Future.resolve([["Git Repository", color.yellow("Outside"), "Not a git repository"]])); + } + + private collectGitRows(): Future { + return Future.concurrently; base: Maybe; pr: pr.PrLookup }>({ + branch: repo.findCurrentBranch(), + base: repo.findBaseBranch(), + pr: pr.getOpenPullRequest() + }).map(({ branch, base, pr: prLookup }): CheckRow[] => [renderBranchRow(branch), renderBaseRow(base), renderPrRow(prLookup)]); + } + + private renderTable(rows: CheckRow[], elapsedMs: number): void { const table = new Table({ head: [color.cyan("Check"), color.cyan("Status"), color.cyan("Info")], colWidths: [20, 15, 40] @@ -108,11 +131,39 @@ class Doctor { if (!hasConfig) { process.stdout.write(color.yellow("! Please run 'commit-tools setup' to configure your API key.\n\n")); } else { - process.stdout.write(color.green("System is ready to generate commits!\n\n")); + process.stdout.write(color.green(`System is ready to generate commits! Done in ${(elapsedMs / 1000).toFixed(2)}s`)); } } } +function renderBranchRow(branch: Maybe): CheckRow { + return branch instanceof Just ? ["Branch", color.green("Current"), branch.value] : ["Branch", color.yellow("Unknown"), "Could not read current branch"]; +} + +function renderBaseRow(base: Maybe): CheckRow { + return base instanceof Just ? ["Base", color.green("Detected"), base.value] : ["Base", color.yellow("Unknown"), "Could not resolve base branch"]; +} + +function renderPrRow(lookup: pr.PrLookup): CheckRow { + switch (lookup.type) { + case "found": + return ["Pull Request", color.green("Open"), `#${lookup.pr.number} ${lookup.pr.url}`]; + case "not-found": + return ["Pull Request", color.yellow("None"), "No open PR for this branch"]; + case "unauthenticated": + return ["Pull Request", color.yellow("Auth"), "Run 'gh auth login' to enable PR lookup"]; + case "unavailable": + return ["Pull Request", color.gray("Skipped"), "gh not installed or remote is not GitHub"]; + default: + return absurd(lookup, "PrLookup"); + } +} + +function renderModelInfo(ai: ProviderConfig): string { + const base = `${ai.model}`; + return ai.effort instanceof Just ? `${base} (${ai.effort.value} effort)` : base; +} + function authMethodLabel(authMethod: AuthMethod): string { switch (authMethod) { case "google_oauth": diff --git a/src/cli/effort.ts b/src/cli/effort.ts new file mode 100644 index 0000000..cd4a2c4 --- /dev/null +++ b/src/cli/effort.ts @@ -0,0 +1,32 @@ +export { EffortCommand }; + +import * as p from "@clack/prompts"; + +import { Future } from "@/libs/future"; +import { type Config } from "@/domain/config/config"; +import { loadConfig, saveConfig } from "@/infra/storage/config"; +import { selectEffortForProvider } from "@/domain/llm/effort"; + +import color from "picocolors"; + +class EffortCommand { + private constructor(private readonly config: Config) {} + + static create(): Future { + return loadConfig() + .chainRej(() => Future.reject(new Error("No configuration found. Run 'commit-tools setup' first."))) + .map((config) => new EffortCommand(config)); + } + + run(): Future { + p.intro(color.bgCyan(color.black(" Change Effort "))); + + return selectEffortForProvider(this.config.ai) + .chain((ai) => saveConfig({ ...this.config, ai })) + .map(() => p.outro(color.green("Effort updated successfully!"))) + .mapRej((e) => { + p.log.error(color.red(e.message)); + return e; + }); + } +} diff --git a/src/cli/model.ts b/src/cli/model.ts index fb83e06..b98fcf5 100644 --- a/src/cli/model.ts +++ b/src/cli/model.ts @@ -6,6 +6,7 @@ import { Future } from "@/libs/future"; import { type Config, type ProviderConfig } from "@/domain/config/config"; import { loadConfig, saveConfig } from "@/infra/storage/config"; import { resolveProvider } from "@/domain/llm/auth-resolver"; +import { selectEffortForProvider, withModel } from "@/domain/llm/effort"; import { fetchModels } from "@/domain/commit/models"; import { selectModelInteractively } from "@/infra/ui/model-picker"; import { loading } from "@/infra/ui/spinner"; @@ -20,30 +21,18 @@ class ModelCommand { static create(): Future { return loadConfig() - .chainRej(() => - Future.reject(new Error("No configuration found. Run 'commit-tools setup' first.")) - ) + .chainRej(() => Future.reject(new Error("No configuration found. Run 'commit-tools setup' first."))) .chain((config) => resolveProvider(config).map((ai) => new ModelCommand(config, ai))); } run(): Future { p.intro(color.bgCyan(color.black(" Change Model "))); - return loading( - "Fetching available models...", - "Models fetched!", - fetchModels(this.providerConfig.provider, this.providerConfig.auth_method) - ) + return loading("Fetching available models...", "Models fetched!", fetchModels(this.providerConfig.provider, this.providerConfig.auth_method)) .chain((models) => selectModelInteractively(models)) - .chain((modelId) => - saveConfig({ - ...this.config, - ai: { ...this.config.ai, model: modelId } - }) - ) - .map(() => { - p.outro(color.green("Model updated successfully!")); - }) + .chain((modelId) => selectEffortForProvider(withModel(this.config.ai, modelId))) + .chain((ai) => saveConfig({ ...this.config, ai })) + .map(() => p.outro(color.green("Model updated successfully!"))) .mapRej((e) => { p.log.error(color.red(e.message)); return e; diff --git a/src/cli/parser.ts b/src/cli/parser.ts index 1325a9b..67f1fe8 100644 --- a/src/cli/parser.ts +++ b/src/cli/parser.ts @@ -3,12 +3,14 @@ export { type CliCommand, parseArgs, showHelp, showVersion }; import * as D from "@/libs/json/decoder"; import { Result } from "@/libs/result"; +import { version as packageVersion } from "@/package.json"; type CliCommand = | { type: "generate" } | { type: "setup" } | { type: "doctor" } | { type: "model" } + | { type: "effort" } | { type: "version" } | { type: "help" }; @@ -25,6 +27,8 @@ const cliCommandDecoder: D.Decoder = D.array(D.string).chain((args) return D.succeed({ type: "doctor" as const }); case "model": return D.succeed({ type: "model" as const }); + case "effort": + return D.succeed({ type: "effort" as const }); case "--version": case "-v": return D.succeed({ type: "version" as const }); @@ -36,8 +40,7 @@ const cliCommandDecoder: D.Decoder = D.array(D.string).chain((args) } }); -const parseArgs = (args: string[]): Result => - D.decode(args, cliCommandDecoder).mapFailure((err) => new Error(err)); +const parseArgs = (args: string[]): Result => D.decode(args, cliCommandDecoder).mapFailure((err) => new Error(err)); const showHelp = (): void => { console.log(` @@ -49,6 +52,7 @@ Commands: login Alias for setup (re-authenticate) doctor Check installation and environment model Select a different AI model + effort Adjust the reasoning effort for the current model --version, -v Show version --help, -h Show help `); @@ -56,7 +60,7 @@ Commands: const showVersion = (): void => { const start = performance.now(); - console.log("commit-tools 0.2.0 (node)"); + console.log(`commit-tools ${packageVersion} (node)`); const elapsed = performance.now() - start; console.log(`Done in ${elapsed.toLocaleString()}ms`); }; diff --git a/src/cli/setup.ts b/src/cli/setup.ts index d956470..d5229e4 100644 --- a/src/cli/setup.ts +++ b/src/cli/setup.ts @@ -1,9 +1,9 @@ export { Setup }; import * as p from "@clack/prompts"; -import type { Option } from "@clack/prompts"; import { Future } from "@/libs/future"; +import { type Option } from "@clack/prompts"; import { saveConfig } from "@/infra/storage/config"; import { CommitConvention, type Config, type ProviderConfig } from "@/domain/config/config"; import { performOAuthFlow, validateOAuthTokens } from "@/infra/auth/google"; @@ -13,6 +13,7 @@ import { Just, Nothing } from "@/libs/maybe"; import { loading } from "@/infra/ui/spinner"; import { fetchModels } from "@/domain/commit/models"; import { selectModelInteractively } from "@/infra/ui/model-picker"; +import { selectEffortForProvider, seedProviderConfig } from "@/domain/llm/effort"; import color from "picocolors"; @@ -94,13 +95,9 @@ class Setup { } } - private buildConfig(authMethod: ProviderConfig["auth_method"], model: string): Config { + private buildConfig(ai: ProviderConfig): Config { return { - ai: { - provider: this.preferences.provider, - model, - auth_method: authMethod - }, + ai, commit_convention: this.preferences.convention, custom_template: this.preferences.customTemplate ? Just(this.preferences.customTemplate) : Nothing() }; @@ -155,13 +152,10 @@ class Setup { } private finalizeSetup(authMethod: ProviderConfig["auth_method"]): Future { - return loading( - "Fetching available models...", - "Models fetched!", - fetchModels(this.preferences.provider, authMethod) - ) + return loading("Fetching available models...", "Models fetched!", fetchModels(this.preferences.provider, authMethod)) .chain((models) => selectModelInteractively(models)) - .chain((modelId) => saveConfig(this.buildConfig(authMethod, modelId))) + .chain((modelId) => selectEffortForProvider(seedProviderConfig(this.preferences.provider, modelId, authMethod))) + .chain((ai) => saveConfig(this.buildConfig(ai))) .map(() => { p.outro(color.green("Setup complete!")); }) @@ -232,8 +226,7 @@ type ApiKeyPrompt = { readonly validate: (value: string | undefined) => string | undefined; }; -const genericApiKeyValidator = (value: string | undefined): string | undefined => - !value || value.length < 10 ? "API Key is too short" : undefined; +const genericApiKeyValidator = (value: string | undefined): string | undefined => (!value || value.length < 10 ? "API Key is too short" : undefined); function apiKeyPromptFor(provider: ProviderConfig["provider"]): ApiKeyPrompt { switch (provider) { diff --git a/src/domain/commit/models.ts b/src/domain/commit/models.ts index c396680..74673bc 100644 --- a/src/domain/commit/models.ts +++ b/src/domain/commit/models.ts @@ -41,9 +41,7 @@ const fetchOpenAIModelsWithOAuth = (tokens: ProviderConfig["auth_method"]["conte } const data = (await response.json()) as { models: CodexModel[] }; - return data.models - .sort((a, b) => a.slug.localeCompare(b.slug)) - .map((m) => ({ id: m.slug, description: m.description })); + return data.models.sort((a, b) => a.slug.localeCompare(b.slug)).map((m) => ({ id: m.slug, description: m.description })); }) ); @@ -112,15 +110,10 @@ const fetchAnthropicModels = (authMethod: ProviderConfig["auth_method"]): Future data?: Array<{ id: string; display_name?: string }>; }; - return (data.data ?? []) - .sort((a, b) => a.id.localeCompare(b.id)) - .map((m) => ({ id: m.id, description: m.display_name ?? "" })); + return (data.data ?? []).sort((a, b) => a.id.localeCompare(b.id)).map((m) => ({ id: m.id, description: m.display_name ?? "" })); }); -const fetchModels = ( - provider: ProviderConfig["provider"], - authMethod: ProviderConfig["auth_method"] -): Future => { +const fetchModels = (provider: ProviderConfig["provider"], authMethod: ProviderConfig["auth_method"]): Future => { switch (provider) { case "openai": return fetchOpenAIModels(authMethod); diff --git a/src/domain/commit/prompts.ts b/src/domain/commit/prompts.ts index aadf54a..da1ab56 100644 --- a/src/domain/commit/prompts.ts +++ b/src/domain/commit/prompts.ts @@ -281,9 +281,7 @@ function getRefinePrompt(params: { diff: string; currentMessage: string; adjustm } { return { prompt: - `\n${params.diff}\n\n` + - `\n${params.currentMessage}\n\n` + - `\n${params.adjustment}\n`, + `\n${params.diff}\n\n` + `\n${params.currentMessage}\n\n` + `\n${params.adjustment}\n`, systemInstruction: "You revise commit messages. Use the diff and the user's adjustment to produce a polished commit message. " + "Preserve required formatting rules: SMALL=single line; MEDIUM/LARGE=title, blank line, bullets prefixed with '- '. " + diff --git a/src/domain/config/config.ts b/src/domain/config/config.ts index 4715c2c..986001c 100644 --- a/src/domain/config/config.ts +++ b/src/domain/config/config.ts @@ -5,18 +5,31 @@ export { type RefreshTokens, type AuthMethod, type ProviderConfig, + type OpenAIEffort, + type AnthropicEffort, + type GeminiEffort, type Model, Config, schema_OAuthTokens, schema_OpenAITokens, schema_AuthMethod, schema_ProviderConfig, + resolveAuthMethod, + AI_PROVIDERS, COMMIT_CONVENTIONS, - AI_PROVIDERS + OPENAI_EFFORTS, + ANTHROPIC_EFFORTS, + GEMINI_EFFORTS }; import * as s from "@/libs/json/schema"; +import { absurd } from "@/libs/types"; +import { ThinkingLevel } from "@google/genai"; + +import type OpenAIPkg from "openai"; +import type AnthropicPkg from "@anthropic-ai/sdk"; + const COMMIT_CONVENTIONS = ["conventional", "imperative", "custom"] as const; type CommitConvention = (typeof COMMIT_CONVENTIONS)[number]; @@ -60,25 +73,49 @@ const schema_AuthMethod = s.discriminatedUnion([ ]); type AuthMethod = s.Infer["type"]; +const OPENAI_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh"] as const satisfies readonly NonNullable[]; +const ANTHROPIC_EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const satisfies readonly NonNullable[]; +const GEMINI_EFFORTS = [ThinkingLevel.MINIMAL, ThinkingLevel.LOW, ThinkingLevel.MEDIUM, ThinkingLevel.HIGH] as const satisfies readonly ThinkingLevel[]; + +type OpenAIEffort = (typeof OPENAI_EFFORTS)[number]; +type AnthropicEffort = (typeof ANTHROPIC_EFFORTS)[number]; +type GeminiEffort = (typeof GEMINI_EFFORTS)[number]; + const schema_ProviderConfig = s.discriminatedUnion([ s.variant({ provider: "gemini", model: s.string, - auth_method: schema_AuthMethod + auth_method: schema_AuthMethod, + effort: s.optionalMaybe(s.stringEnum([...GEMINI_EFFORTS])) }), s.variant({ provider: "openai", model: s.string, - auth_method: schema_AuthMethod + auth_method: schema_AuthMethod, + effort: s.optionalMaybe(s.stringEnum([...OPENAI_EFFORTS])) }), s.variant({ provider: "anthropic", model: s.string, - auth_method: schema_AuthMethod + auth_method: schema_AuthMethod, + effort: s.optionalMaybe(s.stringEnum([...ANTHROPIC_EFFORTS])) }) ]); type ProviderConfig = s.Infer; +const resolveAuthMethod = (ai: ProviderConfig, auth_method: ProviderConfig["auth_method"]): ProviderConfig => { + switch (ai.provider) { + case "openai": + return { provider: "openai", model: ai.model, auth_method, effort: ai.effort }; + case "anthropic": + return { provider: "anthropic", model: ai.model, auth_method, effort: ai.effort }; + case "gemini": + return { provider: "gemini", model: ai.model, auth_method, effort: ai.effort }; + default: + return absurd(ai, "ProviderConfig"); + } +}; + const Config = s.object({ ai: schema_ProviderConfig, commit_convention: s.stringEnum([...COMMIT_CONVENTIONS]), diff --git a/src/domain/llm/auth-error.ts b/src/domain/llm/auth-error.ts new file mode 100644 index 0000000..4908768 --- /dev/null +++ b/src/domain/llm/auth-error.ts @@ -0,0 +1,7 @@ +export { unsupportedAuth }; + +import { Future } from "@/libs/future"; +import { type AuthMethod, type ProviderConfig } from "@/domain/config/config"; + +const unsupportedAuth = (provider: ProviderConfig["provider"], authType: AuthMethod): Future => + Future.reject(new Error(`Unsupported auth method for ${provider}: ${authType}`)); diff --git a/src/domain/llm/auth-resolver.ts b/src/domain/llm/auth-resolver.ts index c73328f..ccce46c 100644 --- a/src/domain/llm/auth-resolver.ts +++ b/src/domain/llm/auth-resolver.ts @@ -2,7 +2,7 @@ export { resolveProvider }; import { Future } from "@/libs/future"; import { Just, Nothing, type Maybe } from "@/libs/maybe"; -import { type Config, type ProviderConfig, type RefreshTokens } from "@/domain/config/config"; +import { resolveAuthMethod, type Config, type ProviderConfig, type RefreshTokens } from "@/domain/config/config"; import { ensureFreshTokens } from "@/infra/auth/google"; import { ensureFreshOpenAITokens } from "@/infra/auth/openai"; import { updateGoogleTokens, updateOpenAITokens } from "@/infra/storage/config"; @@ -12,12 +12,7 @@ type DetectTokenChange = (original: T, fresh: T) => May type RefreshProvider = (tokens: T) => Future; type PersistProvider = (tokens: T) => Future; -type RefreshAndPersistFlow = ( - tokens: T, - refresh: RefreshProvider, - persist: PersistProvider -) => Future; - +type RefreshAndPersistFlow = (tokens: T, refresh: RefreshProvider, persist: PersistProvider) => Future; type ResolveProvider = (config: Config) => Future; const tokensChanged: DetectTokenChange = (original, fresh) => @@ -40,18 +35,14 @@ const resolveProvider: ResolveProvider = (config) => { return Future.resolve(ai); case "google_oauth": - return refreshAndPersist(ai.auth_method.content, ensureFreshTokens, updateGoogleTokens).map((tokens) => ({ - provider: ai.provider, - model: ai.model, - auth_method: { type: "google_oauth", content: tokens } - })); + return refreshAndPersist(ai.auth_method.content, ensureFreshTokens, updateGoogleTokens).map((tokens) => + resolveAuthMethod(ai, { type: "google_oauth", content: tokens }) + ); case "openai_oauth": - return refreshAndPersist(ai.auth_method.content, ensureFreshOpenAITokens, updateOpenAITokens).map((tokens) => ({ - provider: ai.provider, - model: ai.model, - auth_method: { type: "openai_oauth", content: tokens } - })); + return refreshAndPersist(ai.auth_method.content, ensureFreshOpenAITokens, updateOpenAITokens).map((tokens) => + resolveAuthMethod(ai, { type: "openai_oauth", content: tokens }) + ); default: return absurd(ai.auth_method, "AuthMethod"); diff --git a/src/domain/llm/effort.ts b/src/domain/llm/effort.ts new file mode 100644 index 0000000..6db50ae --- /dev/null +++ b/src/domain/llm/effort.ts @@ -0,0 +1,67 @@ +export { seedProviderConfig, withModel, selectEffortForProvider }; + +import { type Future } from "@/libs/future"; +import { type ProviderConfig, type OpenAIEffort, type AnthropicEffort, type GeminiEffort } from "@/domain/config/config"; +import { Nothing } from "@/libs/maybe"; +import { selectOpenAIEffort, selectAnthropicEffort, selectGeminiEffort } from "@/infra/ui/effort-picker"; +import { absurd } from "@/libs/types"; + +const seedProviderConfig = (provider: ProviderConfig["provider"], model: string, auth_method: ProviderConfig["auth_method"]): ProviderConfig => { + switch (provider) { + case "openai": + return { provider, model, auth_method, effort: Nothing() }; + case "anthropic": + return { provider, model, auth_method, effort: Nothing() }; + case "gemini": + return { provider, model, auth_method, effort: Nothing() }; + default: + return absurd(provider, "provider"); + } +}; + +const withModel = (ai: ProviderConfig, model: string): ProviderConfig => { + switch (ai.provider) { + case "openai": + return { provider: "openai", model, auth_method: ai.auth_method, effort: ai.effort }; + case "anthropic": + return { provider: "anthropic", model, auth_method: ai.auth_method, effort: ai.effort }; + case "gemini": + return { provider: "gemini", model, auth_method: ai.auth_method, effort: ai.effort }; + default: + return absurd(ai, "ProviderConfig"); + } +}; + +const selectEffortForProvider = (current: ProviderConfig): Future => { + switch (current.provider) { + case "openai": + return selectOpenAIEffort(current.model, current.effort).map( + (effort): ProviderConfig => ({ + provider: "openai", + model: current.model, + auth_method: current.auth_method, + effort + }) + ); + case "anthropic": + return selectAnthropicEffort(current.model, current.effort).map( + (effort): ProviderConfig => ({ + provider: "anthropic", + model: current.model, + auth_method: current.auth_method, + effort + }) + ); + case "gemini": + return selectGeminiEffort(current.model, current.effort).map( + (effort): ProviderConfig => ({ + provider: "gemini", + model: current.model, + auth_method: current.auth_method, + effort + }) + ); + default: + return absurd(current, "ProviderConfig"); + } +}; diff --git a/src/domain/llm/response-parser.ts b/src/domain/llm/response-parser.ts index 37b0ac3..8e46ac9 100644 --- a/src/domain/llm/response-parser.ts +++ b/src/domain/llm/response-parser.ts @@ -1,79 +1,15 @@ -export { type RawResponse, extractResponse, finalizeText }; +export { type RawResponse, extractResponse }; +import { Maybe, Just, Nothing } from "@/libs/maybe"; import { Future } from "@/libs/future"; -import { absurd } from "@/libs/types"; -const EMPTY_RESPONSE_ERROR = "Empty AI response"; +type RawResponse = { text: Maybe }; -const finalizeText = (raw: string | null | undefined): Future => { - const trimmed = (raw ?? "").trim(); - return trimmed.length === 0 ? Future.reject(new Error(EMPTY_RESPONSE_ERROR)) : Future.resolve(trimmed); -}; - -type TextBlock = { type: "text"; text: string }; -type AnthropicContent = Array<{ type: string; text?: string }>; - -type GeminiSDKLike = { response: { text: () => string | null | undefined } }; -type GeminiRESTLike = { - candidates?: Array<{ content?: { parts?: Array<{ text?: string }> } }>; -}; - -type OpenAIDirectLike = { output_text?: string | null }; -type OpenAIStreamLike = { - response: { - output: Array<{ type: string; content?: Array<{ type: string; text?: string }> }>; - output_text?: string | null; - }; - doneEventText: string; - deltaSnapshotText: string; -}; - -type RawResponse = - | { provider: "gemini"; source: "sdk"; value: GeminiSDKLike } - | { provider: "gemini"; source: "rest"; value: GeminiRESTLike } - | { provider: "anthropic"; value: { content: AnthropicContent } } - | { provider: "openai"; source: "direct"; value: OpenAIDirectLike } - | { provider: "openai"; source: "stream"; value: OpenAIStreamLike }; - -const extractAnthropicText = (content: AnthropicContent): string => - content - .filter((b): b is TextBlock => b.type === "text" && typeof b.text === "string") - .map((b) => b.text) - .join(""); - -const extractOpenAIStreamText = (raw: OpenAIStreamLike): string => { - const fromOutput = raw.response.output - .flatMap((item) => (item.type === "message" ? (item.content ?? []) : [])) - .map((c) => (c.type === "output_text" ? (c.text ?? "") : "")) - .join(""); - - const candidates = [fromOutput, raw.response.output_text ?? "", raw.doneEventText, raw.deltaSnapshotText]; - return candidates.find((v) => v.trim().length > 0) ?? ""; -}; - -const extractResponse = (raw: RawResponse): Future => { - switch (raw.provider) { - case "gemini": - switch (raw.source) { - case "sdk": - return finalizeText(raw.value.response.text()); - case "rest": - return finalizeText(raw.value.candidates?.[0]?.content?.parts?.[0]?.text); - default: - return absurd(raw, "RawResponse.gemini"); - } - case "anthropic": - return finalizeText(extractAnthropicText(raw.value.content)); - case "openai": - switch (raw.source) { - case "direct": - return finalizeText(raw.value.output_text); - case "stream": - return finalizeText(extractOpenAIStreamText(raw.value)); - default: - return absurd(raw, "RawResponse.openai"); - } - default: - return absurd(raw, "RawResponse"); - } -}; +const extractResponse = (raw: RawResponse): Future => + raw.text + .map((s) => s.trim()) + .chain((s) => (s.length === 0 ? Nothing() : Just(s))) + .unwrap( + () => Future.reject(new Error("Response text is empty or missing")), + (s) => Future.resolve(s) + ); diff --git a/src/domain/llm/router.ts b/src/domain/llm/router.ts index ddaa7e8..231b253 100644 --- a/src/domain/llm/router.ts +++ b/src/domain/llm/router.ts @@ -31,9 +31,5 @@ const generateCommitMessage = ( customTemplate: Maybe = Nothing() ): Future => generateContent(config, { prompt: getPrompt(diff, convention, customTemplate) }); -const refineCommitMessage = ( - config: ProviderConfig, - currentMessage: string, - adjustment: string, - diff: string -): Future => generateContent(config, getRefinePrompt({ diff, currentMessage, adjustment })); +const refineCommitMessage = (config: ProviderConfig, currentMessage: string, adjustment: string, diff: string): Future => + generateContent(config, getRefinePrompt({ diff, currentMessage, adjustment })); diff --git a/src/infra/auth/google.ts b/src/infra/auth/google.ts index 633718f..03beb4a 100644 --- a/src/infra/auth/google.ts +++ b/src/infra/auth/google.ts @@ -1,7 +1,7 @@ export { performOAuthFlow, createAuthenticatedClient, ensureFreshTokens, validateOAuthTokens, getAccessToken }; import { type OAuthTokens } from "@/domain/config/config"; -import { SUCCESS_HTML, ERROR_HTML } from "@/infra/auth/templates"; +import { GOOGLE_SUCCESS_HTML, ERROR_HTML } from "@/infra/auth/templates"; import { OAuth2Client, CodeChallengeMethod } from "google-auth-library"; import { Future } from "@/libs/future"; import { environment } from "@/infra/env"; @@ -34,11 +34,7 @@ const findAvailablePort = (): Future => Future.create((reject, resolve) => { const tryPort = (port: number): void => { if (port > PORT_RANGE_END) { - reject( - new Error( - `No available port found in range ${PORT_RANGE_START}-${PORT_RANGE_END}. Close other applications and try again.` - ) - ); + reject(new Error(`No available port found in range ${PORT_RANGE_START}-${PORT_RANGE_END}. Close other applications and try again.`)); return; } @@ -103,7 +99,7 @@ const startCallbackServer = (port: number, state: string): Future { @@ -131,12 +127,7 @@ const openBrowser = (url: string): Future => return Future.resolve(undefined); }); -const exchangeCodeForTokens = ( - client: OAuth2Client, - code: string, - codeVerifier: string, - redirectUri: string -): Future => +const exchangeCodeForTokens = (client: OAuth2Client, code: string, codeVerifier: string, redirectUri: string): Future => Future.attemptP(async () => { const { tokens } = await client.getToken({ code, @@ -179,29 +170,15 @@ const performOAuthFlow = (): Future => state }); - return Future.bracket( - startCallbackServer(port, state), - stopCallbackServer, - (cs) => { - const waitForCode: Future = openBrowser(authUrl).chain(() => - Future.attemptP(() => cs.codePromise) - ); - - const timeout: Future = Future.create((reject) => { - return () => - clearTimeout( - setTimeout( - () => reject(new Error("OAuth flow timed out after 5 minutes. Please try again.")), - OAUTH_TIMEOUT_MS - ) - ); - }); + return Future.bracket(startCallbackServer(port, state), stopCallbackServer, (cs) => { + const waitForCode: Future = openBrowser(authUrl).chain(() => Future.attemptP(() => cs.codePromise)); - return Future.race(waitForCode, timeout).chain((code) => - exchangeCodeForTokens(client, code, codeVerifier, redirectUri) - ); - } - ); + const timeout: Future = Future.create((reject) => { + return () => clearTimeout(setTimeout(() => reject(new Error("OAuth flow timed out after 5 minutes. Please try again.")), OAUTH_TIMEOUT_MS)); + }); + + return Future.race(waitForCode, timeout).chain((code) => exchangeCodeForTokens(client, code, codeVerifier, redirectUri)); + }); }); const createAuthenticatedClient = (tokens: OAuthTokens): Future => { @@ -249,23 +226,17 @@ const ensureFreshTokens = (tokens: OAuthTokens): Future => { .mapRej((err) => { const message = String(err); if (message.includes("invalid_grant")) { - return new Error( - "OAuth tokens have been revoked. Please run 'commit-tools setup' or 'commit-tools login' to re-authenticate." - ); + return new Error("OAuth tokens have been revoked. Please run 'commit-tools setup' or 'commit-tools login' to re-authenticate."); } if (message.includes("invalid_client")) { - return new Error( - "OAuth client credentials are invalid. Check GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in your .env file." - ); + return new Error("OAuth client credentials are invalid. Check GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in your .env file."); } return new Error(`Token refresh failed: ${message}`); }); }; const validateOAuthTokens = (tokens: OAuthTokens): Future => - tokens.access_token && tokens.access_token.length > 0 ? - Future.resolve(undefined) - : Future.reject(new Error("No valid access token available")); + tokens.access_token && tokens.access_token.length > 0 ? Future.resolve(undefined) : Future.reject(new Error("No valid access token available")); const getAccessToken = (tokens: OAuthTokens): Future => tokens.access_token ? Future.resolve(tokens.access_token) : Future.reject(new Error("No access token provided")); diff --git a/src/infra/auth/openai.ts b/src/infra/auth/openai.ts index 3e261c4..647c7bd 100644 --- a/src/infra/auth/openai.ts +++ b/src/infra/auth/openai.ts @@ -171,27 +171,16 @@ const performOpenAIOAuthFlow = (): Future => authUrl.searchParams.set("state", state); authUrl.searchParams.set("originator", "codex_cli_rs"); - return Future.bracket( - startCallbackServer(port, state), - stopCallbackServer, - (cs) => { - const waitForCode: Future = openBrowser(authUrl.toString()).chain(() => - Future.attemptP(() => cs.codePromise) - ); - - const timeout: Future = Future.create((reject) => { - const timer = setTimeout( - () => reject(new Error("OAuth flow timed out after 5 minutes. Please try again.")), - OAUTH_TIMEOUT_MS - ); - return () => clearTimeout(timer); - }); - - return Future.race(waitForCode, timeout).chain((code) => - exchangeCodeForTokens(code, codeVerifier, redirectUri) - ); - } - ); + return Future.bracket(startCallbackServer(port, state), stopCallbackServer, (cs) => { + const waitForCode: Future = openBrowser(authUrl.toString()).chain(() => Future.attemptP(() => cs.codePromise)); + + const timeout: Future = Future.create((reject) => { + const timer = setTimeout(() => reject(new Error("OAuth flow timed out after 5 minutes. Please try again.")), OAUTH_TIMEOUT_MS); + return () => clearTimeout(timer); + }); + + return Future.race(waitForCode, timeout).chain((code) => exchangeCodeForTokens(code, codeVerifier, redirectUri)); + }); }); const ensureFreshOpenAITokens = (tokens: OpenAITokens): Future => { @@ -242,11 +231,7 @@ const ensureFreshOpenAITokens = (tokens: OpenAITokens): Future => - tokens.access_token && tokens.access_token.length > 0 ? - Future.resolve(undefined) - : Future.reject(new Error("No valid OpenAI access token available")); + tokens.access_token && tokens.access_token.length > 0 ? Future.resolve(undefined) : Future.reject(new Error("No valid OpenAI access token available")); const getOpenAIAccessToken = (tokens: OpenAITokens): Future => - tokens.access_token ? - Future.resolve(tokens.access_token) - : Future.reject(new Error("No OpenAI access token provided")); + tokens.access_token ? Future.resolve(tokens.access_token) : Future.reject(new Error("No OpenAI access token provided")); diff --git a/src/infra/auth/templates.ts b/src/infra/auth/templates.ts index 2176585..a339385 100644 --- a/src/infra/auth/templates.ts +++ b/src/infra/auth/templates.ts @@ -1,4 +1,4 @@ -export { COMMON_STYLE, SUCCESS_HTML, ERROR_HTML }; +export { COMMON_STYLE, SUCCESS_HTML, GOOGLE_SUCCESS_HTML, ERROR_HTML }; const COMMON_STYLE = ` `; -const SUCCESS_HTML = ` +const successHtml = (notice?: string): string => ` @@ -79,10 +94,21 @@ const SUCCESS_HTML = `

Authentication Successful

You have successfully connected your account. You can now close this tab and return to your terminal.

+ ${notice ?? ""} `; +const SUCCESS_HTML = successHtml(); + +const GOOGLE_OAUTH_NOTICE = ` +
+ Google OAuth: It can take 1-2 minutes after this page appears for the terminal to + continue. Keep the terminal open while it finishes. +
`; + +const GOOGLE_SUCCESS_HTML = successHtml(GOOGLE_OAUTH_NOTICE); + const ERROR_HTML = (message: string): string => ` diff --git a/src/infra/git/repo.ts b/src/infra/git/repo.ts index 3a568bb..6be4a94 100644 --- a/src/infra/git/repo.ts +++ b/src/infra/git/repo.ts @@ -4,11 +4,16 @@ export { performCommit, performPush, getCurrentBranch, + findCurrentBranch, hasUpstream, getUpstream, + getBaseBranch, + findBaseBranch, getCommitMetadata, + findCommitMetadata, getRemoteUrl, getTrackingRemoteUrl, + findTrackingRemoteUrl, type CommitMetadata, type PushResult, type PushRange @@ -16,7 +21,10 @@ export { import { Future } from "@/libs/future"; import { Just, Nothing, type Maybe } from "@/libs/maybe"; -import { execBin } from "@/infra/shell"; +import { type Result, Success, Failure } from "@/libs/result"; +import { absurd } from "@/libs/types"; +import { execBin, type CommandFailure } from "@/infra/shell"; +import * as Decoder from "@/libs/json/decoder"; import { unlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -37,13 +45,30 @@ type PushResult = { range: Maybe; }; +type BaseLookupError = { type: "reflog-empty" } | { type: "reflog-not-creation"; subject: string } | { type: "reflog-cmd-failed"; message: string }; + +const CREATED_FROM_RE = /^branch: Created from (\S+)$/; + +const commandFailureMessage = (failure: CommandFailure, fallbackMsg: string): string => + failure.output.stderr.trim() || failure.output.stdout.trim() || `${failure.error.message}: ${fallbackMsg}`; + const execGitChecked = (args: string[], fallbackMsg: string): Future => - execBin("git", args).chain(({ stdout, stderr, exitCode }) => - exitCode !== 0 ? - Future.reject(new Error(stderr.trim() || stdout.trim() || fallbackMsg)) - : Future.resolve(stdout) + execBin("git", args).chain((result) => + result.either( + (failure) => Future.reject(new Error(commandFailureMessage(failure, fallbackMsg))), + ({ stdout }) => Future.resolve(stdout) + ) ); +const formatCommitOutput = (stdout: string): string => + "\n" + + stdout + .split("\n") + .filter((line) => !line.startsWith("[")) + .join("\n") + .trim() + + "\n"; + const parsePushRange = (output: string): Maybe => { const m = output.match(/([0-9a-f]{7,40})\.\.([0-9a-f]{7,40})/); if (!m) return Nothing(); @@ -51,14 +76,11 @@ const parsePushRange = (output: string): Maybe => { return before && after ? Just({ before, after }) : Nothing(); }; -const checkIsGitRepo = (): Future => - execGitChecked(["rev-parse", "--is-inside-work-tree"], "Not a git repository").map(() => {}); +const checkIsGitRepo = (): Future => execGitChecked(["rev-parse", "--is-inside-work-tree"], "Not a git repository").map(() => {}); const getStagedDiff = (): Future => execGitChecked(["diff", "--staged"], "Failed to get staged changes").chain((stdout) => - stdout.trim() ? - Future.resolve(stdout) - : Future.reject(new Error("No staged changes found")) + stdout.trim() ? Future.resolve(stdout) : Future.reject(new Error("No staged changes found")) ); const performCommit = (message: string): Future => { @@ -67,45 +89,110 @@ const performCommit = (message: string): Future => { Future.attemptP(() => writeFile(tmpPath, message, "utf-8")), () => Future.attemptP(() => unlink(tmpPath).catch(() => {})), () => execBin("git", ["commit", "-F", tmpPath]) - ).chain(({ stdout, stderr, exitCode }) => - exitCode !== 0 ? - Future.reject(new Error(stderr.trim() || stdout.trim() || "Commit failed")) - : Future.resolve( - "\n" + - stdout - .split("\n") - .filter((line) => !line.startsWith("[")) - .join("\n") - .trim() + - "\n" - ) + ).chain((result) => + result.either( + (failure) => Future.reject(new Error(commandFailureMessage(failure, "Commit failed"))), + ({ stdout }) => Future.resolve(formatCommitOutput(stdout)) + ) ); }; const performPush = (branch?: string, publish = false, forceWithLease = false): Future => { const args = publish && branch ? ["push", "--set-upstream", "origin", branch] : ["push"]; if (forceWithLease) args.push("--force-with-lease"); - return execBin("git", args).chain(({ stdout, stderr, exitCode }) => - exitCode !== 0 ? - Future.reject(new Error(stderr.trim() || stdout.trim() || "Push failed")) - : Future.resolve({ - output: stdout + stderr, - range: parsePushRange(stdout + "\n" + stderr) - }) + return execBin("git", args).chain((result) => + result.either( + (failure) => Future.reject(new Error(commandFailureMessage(failure, "Push failed"))), + ({ stdout, stderr }) => + Future.resolve({ + output: stdout + stderr, + range: parsePushRange(stdout + "\n" + stderr) + }) + ) ); }; const getCurrentBranch = (): Future => execGitChecked(["rev-parse", "--abbrev-ref", "HEAD"], "Failed to get current branch").map((s) => s.trim()); +const findCurrentBranch = (): Future> => + getCurrentBranch() + .map>((branch) => Just(branch)) + .chainRej(() => Future.resolve>(Nothing())); + const hasUpstream = (): Future => - execBin("git", ["rev-parse", "--abbrev-ref", "@{u}"]).map(({ exitCode }) => exitCode === 0); + execBin("git", ["rev-parse", "--abbrev-ref", "@{u}"]).map((result) => + result.either( + () => false, + () => true + ) + ); const getUpstream = (): Future> => - execBin("git", ["rev-parse", "--abbrev-ref", "@{u}"]).map(({ stdout, exitCode }) => - exitCode !== 0 ? Nothing() : Just(stdout.trim()) + execBin("git", ["rev-parse", "--abbrev-ref", "@{u}"]).map((result) => + result.either( + () => Nothing(), + ({ stdout }) => Just(stdout.trim()) + ) + ); + +const oldestReflogSubject = (stdout: string): Result => { + const oldest = stdout.split("\n").filter(Boolean).at(-1); + return oldest ? Success(oldest) : Failure({ type: "reflog-empty" }); +}; + +const parseCreatedFrom = (subject: string): Result => { + const source = subject.match(CREATED_FROM_RE)?.[1]; + return source && source !== "HEAD" ? Success(source) : Failure({ type: "reflog-not-creation", subject }); +}; + +const normalizeBranchRef = (ref: string): string => ref.replace(/^refs\/heads\//, "").replace(/^refs\/remotes\/[^/]+\//, ""); + +const parseBaseFromReflog = (stdout: string): Result => + oldestReflogSubject(stdout).chain(parseCreatedFrom).map(normalizeBranchRef); + +const getBaseFromReflog = (branch: string): Future> => + execBin("git", ["log", "-g", "--format=%gs", branch]).map((result) => + result.either( + (failure) => + Failure({ + type: "reflog-cmd-failed", + message: commandFailureMessage(failure, "Failed to read branch reflog") + }), + ({ stdout }) => parseBaseFromReflog(stdout) + ) ); +const getDefaultRemoteBranch = (): Future> => + execBin("git", ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]).map((result) => + result.either( + () => Nothing(), + ({ stdout }) => Just(stdout.trim().replace(/^origin\//, "")) + ) + ); + +const getBaseBranch = (): Future> => + getCurrentBranch().chain((branch) => + getBaseFromReflog(branch).chain((result) => + result.either( + (err) => { + switch (err.type) { + case "reflog-empty": + case "reflog-not-creation": + return getDefaultRemoteBranch(); + case "reflog-cmd-failed": + return Future.reject>(new Error(`git reflog failed: ${err.message}`)); + default: + return absurd(err, "BaseLookupError"); + } + }, + (base) => (base === branch ? getDefaultRemoteBranch() : Future.resolve>(Just(base))) + ) + ) + ); + +const findBaseBranch = (): Future> => getBaseBranch().chainRej(() => Future.resolve>(Nothing())); + const getRemoteUrl = (remote: string = "origin"): Future => execGitChecked(["remote", "get-url", remote], `Failed to read remote '${remote}' url`).map((s) => s.trim()); @@ -120,12 +207,59 @@ const getTrackingRemoteUrl = (): Future => return getRemoteUrl(remote instanceof Just ? remote.value : "origin"); }); +const findTrackingRemoteUrl = (): Future> => + getTrackingRemoteUrl() + .map>((url) => Just(url)) + .chainRej(() => Future.resolve>(Nothing())); + +const nonEmptyString: Decoder.Decoder = Decoder.string.chain((s) => + s.length > 0 ? Decoder.always(s) : Decoder.fail("expected non-empty string") +); + +const isoDate: Decoder.Decoder = nonEmptyString.chain((s) => { + const d = new Date(s); + return isNaN(d.getTime()) ? Decoder.fail(`invalid ISO date: ${s}`) : Decoder.always(d); +}); + +const commitMetadataDecoder: Decoder.Decoder = Decoder.object({ + hash: nonEmptyString, + short: nonEmptyString, + subject: Decoder.string, + authorName: Decoder.string, + authorEmail: Decoder.string, + date: isoDate +}); + +const COMMIT_FIELDS = [ + ["hash", "%H"], + ["short", "%h"], + ["subject", "%s"], + ["authorName", "%an"], + ["authorEmail", "%ae"], + ["date", "%aI"] +] as const; + +const COMMIT_FORMAT = COMMIT_FIELDS.map(([, p]) => p).join("%x00"); +const COMMIT_KEYS = COMMIT_FIELDS.map(([n]) => n); + +const splitCommitFields = (stdout: string): Result> => { + const parts = stdout.replace(/\n$/, "").split("\x00"); + return parts.length === COMMIT_KEYS.length ? + Success(Object.fromEntries(COMMIT_KEYS.map((k, i) => [k, parts[i]]))) + : Failure(`expected ${COMMIT_KEYS.length} fields, got ${parts.length}`); +}; + const getCommitMetadata = (ref: string = "HEAD"): Future => - execGitChecked(["log", "-1", `--format=%H%n%h%n%s%n%an%n%ae%n%aI`, ref], "Failed to read commit metadata").chain( - (stdout) => { - const [hash, short, subject, authorName, authorEmail, iso] = stdout.split("\n"); - return hash && short && subject !== undefined && authorName !== undefined && authorEmail !== undefined && iso ? - Future.resolve({ hash, short, subject, authorName, authorEmail, date: new Date(iso) }) - : Future.reject(new Error("Malformed git log output")); - } + execGitChecked(["log", "-1", `--format=${COMMIT_FORMAT}`, ref], "Failed to read commit metadata").chain((stdout) => + splitCommitFields(stdout) + .chain((obj) => Decoder.decode(obj, commitMetadataDecoder)) + .either( + (msg) => Future.reject(new Error(`Malformed git log output: ${msg}`)), + (md) => Future.resolve(md) + ) ); + +const findCommitMetadata = (ref: string = "HEAD"): Future> => + getCommitMetadata(ref) + .map>((metadata) => Just(metadata)) + .chainRej(() => Future.resolve>(Nothing())); diff --git a/src/infra/github/pr.ts b/src/infra/github/pr.ts index 73bb6b4..6b7da15 100644 --- a/src/infra/github/pr.ts +++ b/src/infra/github/pr.ts @@ -5,20 +5,29 @@ import * as Decoder from "@/libs/json/decoder"; import { Future } from "@/libs/future"; import { Just, Nothing, type Maybe } from "@/libs/maybe"; -import { execBin } from "@/infra/shell"; +import { execBin, type CommandFailure } from "@/infra/shell"; type PullRequest = { url: string; number: number }; -type PrLookup = { type: "found"; pr: PullRequest } | { type: "unauthenticated" } | { type: "unavailable" }; +type PrLookup = { type: "found"; pr: PullRequest } | { type: "not-found" } | { type: "unauthenticated" } | { type: "unavailable" }; +// Matches: git@github.com:owner/repo.git, https://github.com/owner/repo const GITHUB_REPO_RE = /github\.com[:/]([^/\s]+\/[^/\s]+?)(?:\.git)?\/?$/; + +// Matches gh stderr like: "error connecting... gh auth login to authenticate" const GH_UNAUTH_RE = /not logged into|gh auth login|authentication required/i; +// Matches gh stderr like: "no pull requests found for branch " +const GH_NOT_FOUND_RE = /no pull requests? found/i; + const parseGithubRepo = (url: string): Maybe => { const m = url.match(GITHUB_REPO_RE); return m && m[1] ? Just(m[1]) : Nothing(); }; +const isGhUnauthenticated = (text: string): boolean => GH_UNAUTH_RE.test(text); +const isGhPrNotFound = (text: string): boolean => GH_NOT_FOUND_RE.test(text); + const prDecoder: Decoder.Decoder = Decoder.object({ url: Decoder.string, number: Decoder.number @@ -30,16 +39,24 @@ const parsePrJson = (stdout: string): PrLookup => (pr): PrLookup => ({ type: "found", pr }) ); -const classifyFailure = (stderr: string): PrLookup => - GH_UNAUTH_RE.test(stderr) ? { type: "unauthenticated" } : { type: "unavailable" }; +const commandFailureText = (failure: CommandFailure): string => failure.output.stderr.trim() || failure.output.stdout.trim() || failure.error.message; + +const classifyFailure = (failure: CommandFailure): PrLookup => { + const text = commandFailureText(failure); + return ( + isGhUnauthenticated(text) ? { type: "unauthenticated" } + : isGhPrNotFound(text) ? { type: "not-found" } + : { type: "unavailable" } + ); +}; const getOpenPullRequest = (): Future => Future.both(repo.getTrackingRemoteUrl(), repo.getCurrentBranch()) .chain(([remoteUrl, branch]) => { const slug = parseGithubRepo(remoteUrl); return slug instanceof Just ? - execBin("gh", ["pr", "view", branch, "-R", slug.value, "--json", "url,number"]).map( - ({ stdout, stderr, exitCode }) => (exitCode !== 0 ? classifyFailure(stderr) : parsePrJson(stdout)) + execBin("gh", ["pr", "view", branch, "-R", slug.value, "--json", "url,number"]).map((result) => + result.either(classifyFailure, ({ stdout }) => parsePrJson(stdout)) ) : Future.resolve({ type: "unavailable" }); }) diff --git a/src/infra/llm/anthropic.ts b/src/infra/llm/anthropic.ts index bc20a37..82206dc 100644 --- a/src/infra/llm/anthropic.ts +++ b/src/infra/llm/anthropic.ts @@ -2,73 +2,79 @@ export { generateContentWithAnthropic }; import Anthropic from "@anthropic-ai/sdk"; -import { type Config } from "@/domain/config/config"; +import { type Config, type AnthropicEffort } from "@/domain/config/config"; import { type GenerateContentParams } from "@/domain/llm/router"; import { Future } from "@/libs/future"; import { anthropicOAuthHeaders, CLAUDE_CODE_SYSTEM_PROMPT } from "@/infra/auth/anthropic"; import { absurd } from "@/libs/types"; import { extractResponse } from "@/domain/llm/response-parser"; +import { unsupportedAuth } from "@/domain/llm/auth-error"; +import { Just, fromOptional, type Maybe } from "@/libs/maybe"; type AnthropicConfig = Extract; +type SystemParam = NonNullable; -type TextBlock = { type: "text"; text: string }; +const extractAnthropicText = (content: Anthropic.ContentBlock[]): string => + content + .filter((b): b is Anthropic.TextBlock => b.type === "text") + .map((b) => b.text) + .join(""); -const toError = (error: unknown): Error => (error instanceof Error ? error : new Error(String(error))); +const buildParams = ( + model: string, + system: Maybe, + effort: Maybe, + params: GenerateContentParams +): Anthropic.MessageStreamParams => { + const core: Anthropic.MessageStreamParams = { + model, + max_tokens: 16384, + messages: [{ role: "user", content: params.prompt }], + thinking: { type: "adaptive" }, + output_config: { effort: effort.withDefault("medium") } + }; + return system.maybe(core, (s) => ({ ...core, system: s })); +}; -const callAnthropicWithApiKey = (apiKey: string, model: string, params: GenerateContentParams): Future => +const buildSetupTokenSystem = (instruction: Maybe): SystemParam => + instruction.maybe([{ type: "text", text: CLAUDE_CODE_SYSTEM_PROMPT }], (text) => [ + { type: "text", text: CLAUDE_CODE_SYSTEM_PROMPT }, + { type: "text", text } + ]); + +const callAnthropicWithApiKey = (apiKey: string, model: string, effort: Maybe, params: GenerateContentParams): Future => Future.attemptP(async () => { const client = new Anthropic({ apiKey }); - - return await client.messages.create({ - model, - max_tokens: 4096, - ...(params.systemInstruction !== undefined ? { system: params.systemInstruction } : {}), - messages: [{ role: "user", content: params.prompt }] - }); + const stream = client.messages.stream(buildParams(model, fromOptional(params.systemInstruction), effort, params)); + return await stream.finalMessage(); }) - .mapRej(toError) - .chain((response) => extractResponse({ provider: "anthropic", value: response })); + .mapRej((error) => new Error(`Failed to create Anthropic message: ${error instanceof Error ? error.message : String(error)}`)) + .chain((message) => extractResponse({ text: Just(extractAnthropicText(message.content)) })); const callAnthropicWithSetupToken = ( authToken: string, model: string, + effort: Maybe, params: GenerateContentParams ): Future => Future.attemptP(async () => { - const client = new Anthropic({ - apiKey: null, - authToken, - defaultHeaders: anthropicOAuthHeaders() - }); - - const systemBlocks: TextBlock[] = [{ type: "text", text: CLAUDE_CODE_SYSTEM_PROMPT }]; - - if (params.systemInstruction !== undefined) { - systemBlocks.push({ type: "text", text: params.systemInstruction }); - } - - return await client.messages.create({ - model, - max_tokens: 4096, - system: systemBlocks, - messages: [{ role: "user", content: params.prompt }] - }); + const client = new Anthropic({ apiKey: null, authToken, defaultHeaders: anthropicOAuthHeaders() }); + const system = Just(buildSetupTokenSystem(fromOptional(params.systemInstruction))); + const stream = client.messages.stream(buildParams(model, system, effort, params)); + return await stream.finalMessage(); }) - .mapRej(toError) - .chain((response) => extractResponse({ provider: "anthropic", value: response })); + .mapRej((error) => new Error(`Failed to create Anthropic message: ${error instanceof Error ? error.message : String(error)}`)) + .chain((message) => extractResponse({ text: Just(extractAnthropicText(message.content)) })); -const generateContentWithAnthropic = ( - config: AnthropicConfig, - params: GenerateContentParams -): Future => { +const generateContentWithAnthropic = (config: AnthropicConfig, params: GenerateContentParams): Future => { switch (config.auth_method.type) { case "api_key": - return callAnthropicWithApiKey(config.auth_method.content, config.model, params); + return callAnthropicWithApiKey(config.auth_method.content, config.model, config.effort, params); case "anthropic_setup_token": - return callAnthropicWithSetupToken(config.auth_method.content, config.model, params); + return callAnthropicWithSetupToken(config.auth_method.content, config.model, config.effort, params); case "google_oauth": case "openai_oauth": - return Future.reject(new Error(`Unsupported auth method for Anthropic: ${config.auth_method.type}`)); + return unsupportedAuth("anthropic", config.auth_method.type); default: return absurd(config.auth_method, "AuthMethod"); } diff --git a/src/infra/llm/gemini.ts b/src/infra/llm/gemini.ts index 3d28a38..d5473d8 100644 --- a/src/infra/llm/gemini.ts +++ b/src/infra/llm/gemini.ts @@ -1,24 +1,26 @@ export { type GeminiAuthCredentials, generateContentWithGemini, getAuthCredentials }; -import { GoogleGenerativeAI } from "@google/generative-ai"; +import { GoogleGenAI, ThinkingLevel, type Content, type GenerateContentConfig, type GenerateContentResponse, type GenerationConfig } from "@google/genai"; + +import { type Config, type OAuthTokens, type GeminiEffort } from "@/domain/config/config"; +import { type GenerateContentParams } from "@/domain/llm/router"; import { Future } from "@/libs/future"; -import { type Config, type OAuthTokens } from "@/domain/config/config"; import { getAccessToken } from "@/infra/auth/google"; -import { Just, Nothing, type Maybe } from "@/libs/maybe"; -import { type GenerateContentParams } from "@/domain/llm/router"; +import { Just, Nothing, fromOptional, type Maybe } from "@/libs/maybe"; import { extractResponse } from "@/domain/llm/response-parser"; +import { unsupportedAuth } from "@/domain/llm/auth-error"; +import { absurd } from "@/libs/types"; type GeminiConfig = Extract; +type GeminiAuthCredentials = { readonly method: "api_key"; readonly apiKey: string } | { readonly method: "google_oauth"; readonly tokens: OAuthTokens }; -type GeminiAuthCredentials = - | { readonly method: "api_key"; readonly apiKey: string } - | { readonly method: "google_oauth"; readonly tokens: OAuthTokens }; - -const toError = (error: unknown): Error => (error instanceof Error ? error : new Error(String(error))); +type OAuthRequestBody = { + contents: Content[]; + systemInstruction?: Content; + generationConfig?: GenerationConfig; +}; const getAuthCredentials = (config: Config): Maybe => { - if (config.ai.provider !== "gemini") return Nothing(); - switch (config.ai.auth_method.type) { case "google_oauth": return Just({ method: "google_oauth", tokens: config.ai.auth_method.content }); @@ -29,74 +31,89 @@ const getAuthCredentials = (config: Config): Maybe => { } }; -const generateContentWithApiKey = ( - apiKey: string, - model: string, - params: GenerateContentParams -): Future => { - const genAI = new GoogleGenerativeAI(apiKey); - const modelParams: Parameters[0] = { model }; +const buildSDKConfig = (effort: Maybe, params: GenerateContentParams): GenerateContentConfig => { + const core: GenerateContentConfig = { + thinkingConfig: { thinkingLevel: effort.withDefault(ThinkingLevel.MEDIUM) } + }; + return fromOptional(params.systemInstruction).maybe(core, (s) => ({ ...core, systemInstruction: s })); +}; - if (params.systemInstruction !== undefined) { - modelParams.systemInstruction = params.systemInstruction; - } +const buildOAuthBody = (effort: Maybe, params: GenerateContentParams): OAuthRequestBody => { + const core: OAuthRequestBody = { + contents: [{ parts: [{ text: params.prompt }] }], + generationConfig: { thinkingConfig: { thinkingLevel: effort.withDefault(ThinkingLevel.MEDIUM) } } + }; + return fromOptional(params.systemInstruction).maybe(core, (s) => ({ ...core, systemInstruction: { parts: [{ text: s }] } })); +}; - const geminiModel = genAI.getGenerativeModel(modelParams); +const extractSSEEventText = (event: string): string => { + const dataLine = event.split("\n").find((l) => l.startsWith("data: ")); + if (!dataLine) return ""; + const json = JSON.parse(dataLine.slice(6)) as GenerateContentResponse; + return json.candidates?.[0]?.content?.parts?.[0]?.text ?? ""; +}; - return Future.attemptP(async () => await geminiModel.generateContent(params.prompt)) - .mapRej(toError) - .chain((result) => extractResponse({ provider: "gemini", source: "sdk", value: result })); +const accumulateSSEText = async (response: Response): Promise => { + if (!response.ok) throw new Error(`Gemini API error (${response.status}): ${await response.text()}`); + if (!response.body) throw new Error("Gemini stream returned no body"); + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ""; + let text = ""; + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + const events = buffer.split(/\r?\n\r?\n/); + buffer = events.pop() ?? ""; + for (const ev of events) text += extractSSEEventText(ev); + } + return text; }; +const generateContentWithApiKey = (apiKey: string, model: string, effort: Maybe, params: GenerateContentParams): Future => + Future.attemptP(async () => { + const ai = new GoogleGenAI({ apiKey }); + const stream = await ai.models.generateContentStream({ model, contents: params.prompt, config: buildSDKConfig(effort, params) }); + let text = ""; + for await (const chunk of stream) { + if (chunk.text) text += chunk.text; + } + return text; + }) + .mapRej((error) => new Error(`Failed to create Gemini content: ${error instanceof Error ? error.message : String(error)}`)) + .chain((text) => extractResponse({ text: fromOptional(text) })); + const generateContentWithOAuth = ( tokens: OAuthTokens, model: string, + effort: Maybe, params: GenerateContentParams ): Future => getAccessToken(tokens).chain((accessToken) => Future.attemptP(async () => { - const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`; - - const contents = [{ parts: [{ text: params.prompt }] }]; - const body: Record = { contents }; - - if (params.systemInstruction !== undefined) { - body["system_instruction"] = { - parts: [{ text: params.systemInstruction }] - }; - } - + const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?alt=sse`; const response = await fetch(url, { method: "POST", - headers: { - Authorization: `Bearer ${accessToken}`, - "Content-Type": "application/json" - }, - body: JSON.stringify(body) + headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" }, + body: JSON.stringify(buildOAuthBody(effort, params)) }); - - if (!response.ok) { - const errorBody = await response.text(); - throw new Error(`Gemini API error (${response.status}): ${errorBody}`); - } - - return (await response.json()) as { - promptFeedback?: unknown; - usageMetadata?: unknown; - candidates?: Array<{ content?: { parts?: Array<{ text?: string }> } }>; - }; + return await accumulateSSEText(response); }) - .mapRej(toError) - .chain((json) => extractResponse({ provider: "gemini", source: "rest", value: json })) + .mapRej((error) => new Error(`Failed to create Gemini content: ${error instanceof Error ? error.message : String(error)}`)) + .chain((text) => extractResponse({ text: fromOptional(text) })) ); const generateContentWithGemini = (config: GeminiConfig, params: GenerateContentParams): Future => { switch (config.auth_method.type) { case "api_key": - return generateContentWithApiKey(config.auth_method.content, config.model, params); + return generateContentWithApiKey(config.auth_method.content, config.model, config.effort, params); case "google_oauth": - return generateContentWithOAuth(config.auth_method.content, config.model, params); + return generateContentWithOAuth(config.auth_method.content, config.model, config.effort, params); + case "openai_oauth": + case "anthropic_setup_token": + return unsupportedAuth("gemini", config.auth_method.type); default: - return Future.reject(new Error(`Unsupported auth method for Gemini: ${config.auth_method.type}`)); + return absurd(config.auth_method, "AuthMethod"); } }; diff --git a/src/infra/llm/openai.ts b/src/infra/llm/openai.ts index 558f224..d34511d 100644 --- a/src/infra/llm/openai.ts +++ b/src/infra/llm/openai.ts @@ -1,42 +1,49 @@ export { generateContentWithOpenAI }; -import { type Config, type OpenAITokens } from "@/domain/config/config"; +import OpenAI from "openai"; + +import { type Config, type OpenAIEffort } from "@/domain/config/config"; import { type GenerateContentParams } from "@/domain/llm/router"; import { Future } from "@/libs/future"; import { getOpenAIAccessToken } from "@/infra/auth/openai"; import { extractResponse } from "@/domain/llm/response-parser"; - -import OpenAI from "openai"; +import { unsupportedAuth } from "@/domain/llm/auth-error"; +import { absurd } from "@/libs/types"; +import { fromOptional, type Maybe } from "@/libs/maybe"; type OpenAIConfig = Extract; +type StreamBundle = { + response: OpenAI.Responses.Response; + doneEventText: string; + deltaSnapshotText: string; +}; -const toError = (error: unknown): Error => (error instanceof Error ? error : new Error(String(error))); +const extractStreamText = (bundle: StreamBundle): Maybe => { + const fromOutput = bundle.response.output + .flatMap((item) => (item.type === "message" ? item.content : [])) + .map((c) => (c.type === "output_text" ? c.text : "")) + .join(""); -const callOpenAIWithApiKey = (authToken: string, model: string, params: GenerateContentParams): Future => - Future.attemptP(async () => { - const client = new OpenAI({ apiKey: authToken }); - return await client.responses.create({ - model, - instructions: params.systemInstruction ?? null, - input: params.prompt - }); - }) - .mapRej(toError) - .chain((response) => extractResponse({ provider: "openai", source: "direct", value: response })); + const candidates = [fromOutput, bundle.response.output_text, bundle.doneEventText, bundle.deltaSnapshotText]; + return fromOptional(candidates.find((v): v is string => typeof v === "string" && v.trim().length > 0)); +}; -const callOpenAIWithOAuth = (authToken: string, model: string, params: GenerateContentParams): Future => - Future.attemptP(async () => { - const client = new OpenAI({ - baseURL: "https://chatgpt.com/backend-api/codex", - apiKey: authToken - }); +const openaiReasoning = (effort: Maybe): Maybe => effort.map((e) => ({ effort: e })); - const stream = client.responses.stream({ - model, - instructions: params.systemInstruction ?? "", - input: [{ role: "user", content: params.prompt }], - store: false - }); +const buildStreamParams = (model: string, effort: Maybe, params: GenerateContentParams): OpenAI.Responses.ResponseCreateParamsStreaming => { + const core: OpenAI.Responses.ResponseCreateParamsStreaming = { + model, + instructions: params.systemInstruction ?? "", + input: [{ role: "user", content: params.prompt }], + store: false, + stream: true + }; + return openaiReasoning(effort).maybe(core, (r) => ({ ...core, reasoning: r })); +}; + +const callOpenAIStream = (client: OpenAI, model: string, effort: Maybe, params: GenerateContentParams): Future => + Future.attemptP(async () => { + const stream = client.responses.stream(buildStreamParams(model, effort, params)); let deltaSnapshotText = ""; let doneEventText = ""; @@ -52,29 +59,27 @@ const callOpenAIWithOAuth = (authToken: string, model: string, params: GenerateC const response = await stream.finalResponse(); return { response, doneEventText, deltaSnapshotText }; }) - .mapRej(toError) - .chain((bundle) => extractResponse({ provider: "openai", source: "stream", value: bundle })); + .mapRej((error) => new Error(`Failed to create OpenAI response: ${error instanceof Error ? error.message : String(error)}`)) + .chain((bundle) => extractResponse({ text: extractStreamText(bundle) })); -const generateContentWithApiKey = ( - apiKey: string, - model: string, - params: GenerateContentParams -): Future => callOpenAIWithApiKey(apiKey, model, params); +const callOpenAIWithApiKey = (apiKey: string, model: string, effort: Maybe, params: GenerateContentParams): Future => + callOpenAIStream(new OpenAI({ apiKey }), model, effort, params); -const generateContentWithOAuth = ( - tokens: OpenAITokens, - model: string, - params: GenerateContentParams -): Future => - getOpenAIAccessToken(tokens).chain((accessToken) => callOpenAIWithOAuth(accessToken, model, params)); +const callOpenAIWithOAuth = (authToken: string, model: string, effort: Maybe, params: GenerateContentParams): Future => + callOpenAIStream(new OpenAI({ baseURL: "https://chatgpt.com/backend-api/codex", apiKey: authToken }), model, effort, params); const generateContentWithOpenAI = (config: OpenAIConfig, params: GenerateContentParams): Future => { switch (config.auth_method.type) { case "api_key": - return generateContentWithApiKey(config.auth_method.content, config.model, params); + return callOpenAIWithApiKey(config.auth_method.content, config.model, config.effort, params); case "openai_oauth": - return generateContentWithOAuth(config.auth_method.content, config.model, params); + return getOpenAIAccessToken(config.auth_method.content).chain((accessToken) => + callOpenAIWithOAuth(accessToken, config.model, config.effort, params) + ); + case "google_oauth": + case "anthropic_setup_token": + return unsupportedAuth("openai", config.auth_method.type); default: - return Future.reject(new Error(`Unsupported auth method for OpenAI: ${config.auth_method.type}`)); + return absurd(config.auth_method, "AuthMethod"); } }; diff --git a/src/infra/shell.ts b/src/infra/shell.ts index e4e71dd..012f305 100644 --- a/src/infra/shell.ts +++ b/src/infra/shell.ts @@ -1,20 +1,33 @@ -export { execBin, type ExecResult }; +export { execBin, type CommandOutput, type CommandFailure, type ExecResult }; import { Future } from "@/libs/future"; +import { Failure, type Result, Success } from "@/libs/result"; import { spawn } from "node:child_process"; -type ExecResult = { stdout: string; stderr: string; exitCode: number }; +type CommandOutput = Readonly<{ stdout: string; stderr: string }>; +type CommandFailure = Readonly<{ output: CommandOutput; error: Error }>; +type ExecResult = Result; + +const commandResult = (output: CommandOutput, exitCode: number | null, signal: NodeJS.Signals | null): ExecResult => + exitCode === 0 ? + Success(output) + : Failure({ + output, + error: new Error(`Command failed with exit code ${exitCode}${signal ? ` and signal ${signal}` : ""}`) + }); const execBin = (bin: string, args: string[]): Future => Future.create((reject, resolve) => { const proc = spawn(bin, args, { stdio: ["pipe", "pipe", "pipe"] }); + let stdout = ""; let stderr = ""; + proc.stdout.on("data", (d: Buffer) => (stdout += d.toString())); proc.stderr.on("data", (d: Buffer) => (stderr += d.toString())); - proc.on("error", (err) => reject(err instanceof Error ? err : new Error(String(err)))); - proc.on("close", (exitCode) => resolve({ stdout, stderr, exitCode: exitCode ?? 1 })); - return () => { - proc.kill(); - }; + + proc.on("error", (err) => reject(new Error(`Failed to start process: ${err.message}`))); + proc.on("close", (exitCode, signal) => resolve(commandResult({ stdout, stderr }, exitCode, signal))); + + return () => proc.kill(); }); diff --git a/src/infra/storage/config.ts b/src/infra/storage/config.ts index fee888d..de3bee3 100644 --- a/src/infra/storage/config.ts +++ b/src/infra/storage/config.ts @@ -6,8 +6,8 @@ import { Future } from "@/libs/future"; import { resolve } from "node:path"; import { homedir } from "node:os"; import { readFile, writeFile, mkdir } from "node:fs/promises"; -import { Config, type OAuthTokens, type OpenAITokens } from "@/domain/config/config"; -import { Just, Nothing, type Maybe } from "@/libs/maybe"; +import { Config, resolveAuthMethod, type OAuthTokens, type OpenAITokens } from "@/domain/config/config"; +import { absurd } from "@/libs/types"; const CONFIG_DIR = resolve(homedir(), ".commit-tools"); const CONFIG_FILE = resolve(CONFIG_DIR, "config.json"); @@ -30,36 +30,38 @@ const saveConfig = (config: Config): Future => await writeFile(CONFIG_FILE, JSON.stringify(s.encode(Config, config), null, 2), "utf-8"); }); -const extractGoogleOAuthConfig = (config: Config): Maybe<{ model: string }> => - config.ai.provider === "gemini" && config.ai.auth_method.type === "google_oauth" ? - Just({ model: config.ai.model }) - : Nothing(); - -const extractOpenAIOAuthConfig = (config: Config): Maybe<{ model: string }> => - config.ai.provider === "openai" && config.ai.auth_method.type === "openai_oauth" ? - Just({ model: config.ai.model }) - : Nothing(); - const updateGoogleTokens = (tokens: OAuthTokens): Future => - loadConfig().chain((config) => - extractGoogleOAuthConfig(config).maybe( - Future.reject(new Error("Cannot update tokens: not using Google OAuth authentication")), - ({ model }) => - saveConfig({ - ...config, - ai: { provider: "gemini", model, auth_method: { type: "google_oauth", content: tokens } } - }) - ) - ); + loadConfig().chain((config) => { + switch (config.ai.auth_method.type) { + case "google_oauth": + return saveConfig({ + ai: resolveAuthMethod(config.ai, { type: "google_oauth", content: tokens }), + commit_convention: config.commit_convention, + custom_template: config.custom_template + }); + case "api_key": + case "openai_oauth": + case "anthropic_setup_token": + return Future.reject(new Error("Cannot update tokens: not using Google OAuth authentication")); + default: + return absurd(config.ai.auth_method, "AuthMethod"); + } + }); const updateOpenAITokens = (tokens: OpenAITokens): Future => - loadConfig().chain((config) => - extractOpenAIOAuthConfig(config).maybe( - Future.reject(new Error("Cannot update tokens: not using OpenAI OAuth authentication")), - ({ model }) => - saveConfig({ - ...config, - ai: { provider: "openai", model, auth_method: { type: "openai_oauth", content: tokens } } - }) - ) - ); + loadConfig().chain((config) => { + switch (config.ai.auth_method.type) { + case "openai_oauth": + return saveConfig({ + ai: resolveAuthMethod(config.ai, { type: "openai_oauth", content: tokens }), + commit_convention: config.commit_convention, + custom_template: config.custom_template + }); + case "api_key": + case "google_oauth": + case "anthropic_setup_token": + return Future.reject(new Error("Cannot update tokens: not using OpenAI OAuth authentication")); + default: + return absurd(config.ai.auth_method, "AuthMethod"); + } + }); diff --git a/src/infra/ui/effort-picker.ts b/src/infra/ui/effort-picker.ts new file mode 100644 index 0000000..37a8287 --- /dev/null +++ b/src/infra/ui/effort-picker.ts @@ -0,0 +1,58 @@ +export { selectOpenAIEffort, selectAnthropicEffort, selectGeminiEffort }; + +import { ThinkingLevel } from "@google/genai"; + +import { Future } from "@/libs/future"; +import { Just, type Maybe } from "@/libs/maybe"; +import { OPENAI_EFFORTS, ANTHROPIC_EFFORTS, GEMINI_EFFORTS, type OpenAIEffort, type AnthropicEffort, type GeminiEffort } from "@/domain/config/config"; + +type EffortSliderModule = typeof import("@/infra/ui/effort-slider"); + +const selectEffort = (options: readonly V[], modelId: string, currentEffort: Maybe, defaultValue: V): Future> => { + const initialIndex = currentEffort.maybe(Math.max(0, options.indexOf(defaultValue)), (v) => { + const idx = options.indexOf(v); + return idx >= 0 ? idx : Math.max(0, options.indexOf(defaultValue)); + }); + + return Future.attemptP(async () => { + // Lazy-load Ink/React so non-interactive CLI paths don't pay their startup cost. + // Ink pulls in React, Yoga layout, and a render loop — non-trivial to initialize + // even for commands that never reach an interactive prompt (scripted runs, + // --yes flows, piped stdin, git hook integrations). A static top-level import + // would charge every CLI entrypoint for that cost regardless of whether the + // picker is ever shown; dynamic import defers it until the user actually + // reaches this code path. + const { render } = await import("ink"); + const React = await import("react"); + const sliderModule: EffortSliderModule = await import("@/infra/ui/effort-slider"); + return { render, React, sliderModule }; + }).chain(({ render, React, sliderModule }) => + Future.create>((_reject, resolve) => { + const { unmount } = render( + React.createElement(sliderModule.EffortSlider, { + title: `Reasoning effort for ${modelId}`, + options, + initialIndex, + onSubmit: (value: V) => { + unmount(); + resolve(Just(value)); + }, + onCancel: () => { + unmount(); + resolve(currentEffort); + } + }) + ); + return () => unmount(); + }) + ); +}; + +const selectOpenAIEffort = (modelId: string, current: Maybe): Future> => + selectEffort(OPENAI_EFFORTS, modelId, current, "medium"); + +const selectAnthropicEffort = (modelId: string, current: Maybe): Future> => + selectEffort(ANTHROPIC_EFFORTS, modelId, current, "medium"); + +const selectGeminiEffort = (modelId: string, current: Maybe): Future> => + selectEffort(GEMINI_EFFORTS, modelId, current, ThinkingLevel.MEDIUM); diff --git a/src/infra/ui/effort-slider.tsx b/src/infra/ui/effort-slider.tsx new file mode 100644 index 0000000..88d3271 --- /dev/null +++ b/src/infra/ui/effort-slider.tsx @@ -0,0 +1,117 @@ +export { EffortSlider, type EffortSliderProps }; + +import * as React from "react"; + +import { Box, Text, useInput, useApp, type Key } from "ink"; + +import chalk from "chalk"; + +type ChalkColor = "yellow" | "green" | "cyan" | "blueBright" | "magenta" | "red" | "gray"; + +type EffortSliderProps = { + title: string; + options: readonly V[]; + initialIndex: number; + onSubmit: (value: V) => void; + onCancel: () => void; +}; + +const PALETTE: Record = { + 2: ["yellow", "red"], + 3: ["yellow", "green", "red"], + 4: ["yellow", "green", "magenta", "red"], + 5: ["yellow", "green", "cyan", "magenta", "red"], + 6: ["yellow", "green", "cyan", "blueBright", "magenta", "red"] +}; + +const paletteFor = (n: number): readonly ChalkColor[] => PALETTE[n] ?? PALETTE[6]!; + +const colorize = (color: ChalkColor, text: string, bold: boolean): string => (bold ? chalk[color].bold(text) : chalk[color](text)); + +const EffortSlider = ({ title, options, initialIndex, onSubmit, onCancel }: EffortSliderProps) => { + const { exit } = useApp(); + const clamped = Math.max(0, Math.min(options.length - 1, initialIndex)); + const [index, setIndex] = React.useState(clamped); + + const handleLifecycle = (key: Key): boolean => { + if (key.escape) { + onCancel(); + exit(); + return true; + } + if (key.return) { + onSubmit(options[index]!); + exit(); + return true; + } + return false; + }; + + const handleNavigation = (key: Key): boolean => { + if (key.leftArrow) { + setIndex((i) => Math.max(0, i - 1)); + return true; + } + if (key.rightArrow) { + setIndex((i) => Math.min(options.length - 1, i + 1)); + return true; + } + return false; + }; + + useInput((_input, key) => { + if (handleLifecycle(key)) return; + handleNavigation(key); + }); + + const cols = Math.min(72, Math.max(40, (process.stdout.columns ?? 72) - 8)); + const palette = paletteFor(options.length); + const lastIndex = options.length - 1; + const step = lastIndex > 0 ? Math.floor((cols - 1) / lastIndex) : 0; + const markerCol = index * step; + const markerColor = palette[index] ?? "cyan"; + + const railChars = Array.from({ length: cols }).map((_, c) => (c === markerCol ? chalk[markerColor]("▲") : chalk.dim("─"))); + const rail = railChars.join(""); + + const labelParts: string[] = options.map((opt, i) => { + const color = palette[i] ?? "gray"; + return i === index ? colorize(color, opt, true) : chalk.gray(opt); + }); + const labelWidth = (cols - options.join("").length) / Math.max(1, options.length - 1); + const labels = labelParts.join(" ".repeat(Math.max(1, Math.floor(labelWidth)))); + + const spacerLen = Math.max(1, cols - "Speed".length - "Intelligence".length); + return ( + + + + {title} + + + + + + + Speed + {" ".repeat(spacerLen)} + Intelligence + + + + {rail} + + + + {labels.toLocaleLowerCase()} + + + + + + + Use ◀ ▶ to adjust • Enter to confirm • Esc to cancel + + + ); +}; diff --git a/src/infra/ui/model-picker.ts b/src/infra/ui/model-picker.ts index b32e57e..60440d0 100644 --- a/src/infra/ui/model-picker.ts +++ b/src/infra/ui/model-picker.ts @@ -5,11 +5,19 @@ import { Model } from "@/domain/config/config"; const selectModelInteractively = (models: Model[]): Future => Future.attemptP(async () => { + // Lazy-load Ink/React so non-interactive CLI paths don't pay their startup cost. + // Ink pulls in React, Yoga layout, and a render loop — non-trivial to initialize + // even for commands that never reach an interactive prompt (scripted runs, + // --yes flows, piped stdin, git hook integrations). A static top-level import + // would charge every CLI entrypoint for that cost regardless of whether the + // picker is ever shown; dynamic import defers it until the user actually + // reaches this code path. const { render } = await import("ink"); const React = await import("react"); const { ModelSelector } = await import("@/infra/ui/model-selector"); - - return new Promise((resolve, reject) => { + return { render, React, ModelSelector }; + }).chain(({ render, React, ModelSelector }) => + Future.create((reject, resolve) => { const { unmount } = render( React.createElement(ModelSelector, { models, @@ -23,5 +31,6 @@ const selectModelInteractively = (models: Model[]): Future => } }) ); - }); - }); + return () => unmount(); + }) + ); diff --git a/src/infra/ui/model-selector.tsx b/src/infra/ui/model-selector.tsx index 1b64a64..f8f44f0 100644 --- a/src/infra/ui/model-selector.tsx +++ b/src/infra/ui/model-selector.tsx @@ -6,6 +6,8 @@ import { Box, Text, useInput, useApp, type Key } from "ink"; import chalk from "chalk"; +import { search } from "@/libs/fuzzy"; + type Model = { id: string; description: string; @@ -17,20 +19,6 @@ type ModelSelectorProps = { onCancel: () => void; }; -function fuzzyMatch(pattern: string, target: string): boolean { - const p = pattern.toLowerCase(); - const t = target.toLowerCase(); - let pi = 0; - let ti = 0; - - while (pi < p.length && ti < t.length) { - if (p[pi] === t[ti]) pi++; - ti++; - } - - return pi === p.length; -} - function deleteWordLeft(value: string, cursor: number): [string, number] { let i = cursor - 1; while (i > 0 && value[i - 1] === " ") i--; @@ -58,11 +46,7 @@ const ModelSelector = ({ models, onSelect, onCancel }: ModelSelectorProps) => { const [cursorOffset, setCursorOffset] = React.useState(0); const [selectedIndex, setSelectedIndex] = React.useState(0); - const filteredModels = models.filter((m) => { - const q = query.trim(); - if (!q) return true; - return fuzzyMatch(q, m.id) || (m.description && fuzzyMatch(q, m.description)); - }); + const filteredModels = React.useMemo(() => search(query, models, [(m) => m.id, (m) => m.description]).map((r) => r.item), [query, models]); const handleLifecycle = (key: Key): boolean => { if (key.escape) { diff --git a/src/infra/ui/push-note.ts b/src/infra/ui/push-note.ts index d7eb237..43c3da3 100644 --- a/src/infra/ui/push-note.ts +++ b/src/infra/ui/push-note.ts @@ -4,14 +4,14 @@ import * as p from "@clack/prompts"; import type { CommitMetadata, PushRange } from "@/infra/git/repo"; import type { PrLookup } from "@/infra/github/pr"; -import { Just, type Maybe } from "@/libs/maybe"; +import type { Maybe } from "@/libs/maybe"; import { absurd } from "@/libs/types"; type PushMetadata = { - commit: CommitMetadata; - localBranch: string; - upstream: Maybe; - remoteUrl: string; + commit: Maybe; + localBranch: Maybe; + baseBranch: Maybe; + remoteUrl: Maybe; range: Maybe; pr: PrLookup; }; @@ -24,6 +24,7 @@ const renderPrLine = (lookup: PrLookup): string[] => { return [`pr #${lookup.pr.number} ${lookup.pr.url}`]; case "unauthenticated": return [`pr Tip: run 'gh auth login' to show open PR in the current branch...`]; + case "not-found": case "unavailable": return []; default: @@ -31,21 +32,22 @@ const renderPrLine = (lookup: PrLookup): string[] => { } }; +const renderCommitLines = (commit: Maybe): string[] => + commit.maybe([], (value) => [ + `commit ${value.short} ${value.subject}`, + `author ${value.authorName} <${value.authorEmail}>`, + `date ${formatDate(value.date)}` + ]); + const renderPushNote = (m: PushMetadata): void => { - const branchLine = - m.upstream instanceof Just ? `branch ${m.localBranch} → ${m.upstream.value}` : `branch ${m.localBranch}`; - - const rangeLine = m.range instanceof Just ? [`range ${m.range.value.before}..${m.range.value.after}`] : []; - - const body = [ - `commit ${m.commit.short} ${m.commit.subject}`, - `author ${m.commit.authorName} <${m.commit.authorEmail}>`, - `date ${formatDate(m.commit.date)}`, - branchLine, - `remote ${m.remoteUrl}`, - ...rangeLine, - ...renderPrLine(m.pr) - ].join("\n"); + const branchLine = m.localBranch.maybe([], (branch) => [`branch ${branch}`]); + const baseLine = m.baseBranch.maybe([], (base) => [`base ${base}`]); + const remoteLine = m.remoteUrl.maybe([], (url) => [`remote ${url}`]); + const rangeLine = m.range.maybe([], (range) => [`range ${range.before}..${range.after}`]); + + const body = [...renderCommitLines(m.commit), ...branchLine, ...baseLine, ...remoteLine, ...rangeLine, ...renderPrLine(m.pr)].join("\n"); + + if (!body) return; p.note(body, "Pushed"); }; diff --git a/src/libs/future.ts b/src/libs/future.ts index 30c673d..58364a8 100644 --- a/src/libs/future.ts +++ b/src/libs/future.ts @@ -76,10 +76,7 @@ class Future { } static traverse(f: (_: A) => Future, xs: Array
): Future> { - return xs.reduce( - (acc, x) => acc.chain((ys) => f(x).map((y) => [...ys, y])), - Future.resolve([]) as Future> - ); + return xs.reduce((acc, x) => acc.chain((ys) => f(x).map((y) => [...ys, y])), Future.resolve([]) as Future>); } static resolveAfter(milliseconds: number, value: T): Future { diff --git a/src/libs/fuzzy.ts b/src/libs/fuzzy.ts new file mode 100644 index 0000000..d3cc2e9 --- /dev/null +++ b/src/libs/fuzzy.ts @@ -0,0 +1,111 @@ +export { match, search, type Match, type SearchOptions }; + +import { type Maybe, Just, Nothing, mapMaybe } from "@/libs/maybe"; + +type Match = { + score: number; + positions: ReadonlyArray; +}; + +type SearchOptions = { + caseSensitive?: boolean; + separators?: RegExp; +}; + +const SEPARATORS = /[\s\-_./]+/; +const SEPARATOR_CHAR = /[\s\-_./]/; + +const SCORE_MATCH = 16; +const BONUS_BOUNDARY = 8; +const BONUS_CONSECUTIVE = 4; +const PENALTY_GAP_START = -3; +const PENALTY_GAP_EXTEND = -1; + +const applyCase = (s: string, caseSensitive: boolean): string => (caseSensitive ? s : s.toLowerCase()); + +const isBoundary = (target: string, i: number): boolean => { + if (i === 0) return true; + const prev = target[i - 1]; + return prev !== undefined && SEPARATOR_CHAR.test(prev); +}; + +type TokenScore = { score: number; end: number; positions: ReadonlyArray }; +type GapScan = { matchIndex: number; gapPenalty: number }; + +const scanForChar = (target: string, from: number, ch: string): Maybe => { + let ti = from; + let gapPenalty = 0; + let inGap = false; + while (ti < target.length && target[ti] !== ch) { + gapPenalty += inGap ? PENALTY_GAP_EXTEND : PENALTY_GAP_START; + inGap = true; + ti++; + } + return ti >= target.length ? Nothing() : Just({ matchIndex: ti, gapPenalty }); +}; + +const scoreMatchAt = (target: string, ti: number, prevMatchIndex: number): number => { + let score = SCORE_MATCH; + if (isBoundary(target, ti)) score += BONUS_BOUNDARY; + if (prevMatchIndex === ti - 1) score += BONUS_CONSECUTIVE; + return score; +}; + +const scoreToken = (token: string, target: string, from: number): Maybe => { + const positions: number[] = []; + let score = 0; + let prevMatchIndex = -1; + let ti = from; + + for (const ch of token) { + const found = scanForChar(target, ti, ch); + if (found.isNothing()) return Nothing(); + const inner = found.expect("checked above"); + score += inner.gapPenalty + scoreMatchAt(target, inner.matchIndex, prevMatchIndex); + positions.push(inner.matchIndex); + prevMatchIndex = inner.matchIndex; + ti = inner.matchIndex + 1; + } + return Just({ score, end: ti, positions }); +}; + +const scoreAllTokens = (tokens: ReadonlyArray, target: string): Maybe => { + let cursor = 0; + let total = 0; + const allPositions: number[] = []; + for (const tok of tokens) { + const r = scoreToken(tok, target, cursor); + if (r.isNothing()) return Nothing(); + const inner = r.expect("checked above"); + total += inner.score; + allPositions.push(...inner.positions); + cursor = inner.end; + } + return Just({ score: total, positions: allPositions }); +}; + +const match = (query: string, target: string, options?: SearchOptions): Maybe => { + const caseSensitive = options?.caseSensitive ?? /[A-Z]/.test(query); + const sep = options?.separators ?? SEPARATORS; + const tokens = applyCase(query, caseSensitive).split(sep).filter(Boolean); + if (tokens.length === 0) return Just({ score: 0, positions: [] }); + return scoreAllTokens(tokens, applyCase(target, caseSensitive)); +}; + +const search = ( + query: string, + items: ReadonlyArray, + selectors: ReadonlyArray<(item: T) => string>, + options?: SearchOptions +): ReadonlyArray<{ item: T; match: Match }> => { + if (query.trim().length === 0) { + return items.map((item) => ({ item, match: { score: 0, positions: [] } })); + } + const scored = mapMaybe([...items], (item) => { + const matches = mapMaybe([...selectors], (sel) => match(query, sel(item), options)); + if (matches.length === 0) return Nothing<{ item: T; match: Match }>(); + const best = matches.reduce((a, b) => (b.score > a.score ? b : a)); + return Just({ item, match: best }); + }); + return [...scored].sort((a, b) => b.match.score - a.match.score); +}; diff --git a/src/libs/helpers/object.ts b/src/libs/helpers/object.ts index dc3c612..a50e3ca 100644 --- a/src/libs/helpers/object.ts +++ b/src/libs/helpers/object.ts @@ -1,9 +1,6 @@ export { filterKeys, filterMap, mapValues }; -function filterKeys( - obj: O, - pred: (key: KK, value: O[KK]) => boolean -): Partial { +function filterKeys(obj: O, pred: (key: KK, value: O[KK]) => boolean): Partial { const result: Partial = {}; const keys = Object.keys(obj) as K[]; for (const key of keys) { @@ -15,10 +12,7 @@ function filterKeys( } // Filter keys and transform values at the same time -function filterMap( - obj: O, - fn: (key: K, value: O[K]) => R | undefined -): Partial<{ [K in keyof O]: R }> { +function filterMap(obj: O, fn: (key: K, value: O[K]) => R | undefined): Partial<{ [K in keyof O]: R }> { const result = {} as Partial<{ [K in keyof O]: R }>; const keys = Object.keys(obj) as Array; for (const key of keys) { @@ -30,10 +24,7 @@ function filterMap( return result; } -function mapValues( - obj: T, - fn: (key: K, value: T[K]) => R -): { [K in keyof T]: R } { +function mapValues(obj: T, fn: (key: K, value: T[K]) => R): { [K in keyof T]: R } { const out = {} as { [K in keyof T]: R }; const keys = Object.keys(obj) as Array; for (const k of keys) { diff --git a/src/libs/json/decoder.ts b/src/libs/json/decoder.ts index 699961a..fdc301c 100644 --- a/src/libs/json/decoder.ts +++ b/src/libs/json/decoder.ts @@ -127,22 +127,16 @@ const both = (left: Decoder, right: Decoder): Decoder<[T, U]> => return Success([l.value, r.value]); }); -const string: Decoder = new Decoder((v) => - typeof v === "string" ? Success(v) : failure("expected string but found " + typeof v) -); +const string: Decoder = new Decoder((v) => (typeof v === "string" ? Success(v) : failure("expected string but found " + typeof v))); -const number: Decoder = new Decoder((v) => - typeof v === "number" ? Success(v) : failure("expected number but found " + typeof v) -); +const number: Decoder = new Decoder((v) => (typeof v === "number" ? Success(v) : failure("expected number but found " + typeof v))); const stringNumber: Decoder = string.chain((s) => { const v = parseInt(s, 10); return isNaN(v) ? fail("not a valid number: " + s) : succeed(v); }); -const boolean: Decoder = new Decoder((v) => - typeof v === "boolean" ? Success(v) : failure("expected boolean but found " + typeof v) -); +const boolean: Decoder = new Decoder((v) => (typeof v === "boolean" ? Success(v) : failure("expected boolean but found " + typeof v))); const array = (decodeValue: Decoder): Decoder> => new Decoder((input) => { @@ -270,13 +264,9 @@ const maybe = (decoder: Decoder): Decoder> => const nullable = (decoder: Decoder): Decoder> => oneOf([nullP, decoder]); -const nullP: Decoder = new Decoder((v) => - v === null ? Success(null) : failure("expected null but found " + typeof v) -); +const nullP: Decoder = new Decoder((v) => (v === null ? Success(null) : failure("expected null but found " + typeof v))); -const undefinedP: Decoder = new Decoder((v) => - v === undefined ? Success(undefined) : failure("expected `undefined` " + typeof v) -); +const undefinedP: Decoder = new Decoder((v) => (v === undefined ? Success(undefined) : failure("expected `undefined` " + typeof v))); // Useful for parsing tag names in discriminated unions. const stringLiteral = (str: T): Decoder => @@ -297,8 +287,7 @@ class DecoderOptional { const optionalMaybe = (decoder: Decoder): DecoderOptional> => DecoderOptional.from(decoder); -const optionalNullable = (decoder: Decoder>): DecoderOptional> => - optionalMaybe(decoder).map((v) => v.asNullable()); +const optionalNullable = (decoder: Decoder>): DecoderOptional> => optionalMaybe(decoder).map((v) => v.asNullable()); // An object field that may be absent. const optional = (decoder: Decoder): DecoderOptional => diff --git a/src/libs/json/encoder.ts b/src/libs/json/encoder.ts index eb8d029..76d6172 100644 --- a/src/libs/json/encoder.ts +++ b/src/libs/json/encoder.ts @@ -114,8 +114,7 @@ const triple = (sA: Encoder, sB: Encoder, sC: Encoder): Encode const maybe = (encoder: Encoder): Encoder> => new Encoder((input) => (input instanceof Nothing ? { nothing: {} } : { just: encoder.run(input.value) }) as Json); -const nullable = (encoder: Encoder): Encoder> => - new Encoder((input) => (input === null ? null : encoder.run(input))); +const nullable = (encoder: Encoder): Encoder> => new Encoder((input) => (input === null ? null : encoder.run(input))); // An encoder for object keys that omits the field if the value is Nothing. class EncoderOptional { diff --git a/src/libs/json/schema.ts b/src/libs/json/schema.ts index 2064b88..5fe6344 100644 --- a/src/libs/json/schema.ts +++ b/src/libs/json/schema.ts @@ -109,8 +109,7 @@ class SchemaOptional { } // An object field that may be absent. -const optional = (s: Schema): SchemaOptional => - new SchemaOptional(D.optional(s.decoder), E.optional(s.encoder)); +const optional = (s: Schema): SchemaOptional => new SchemaOptional(D.optional(s.decoder), E.optional(s.encoder)); const optionalNullable = (schema: Schema>): SchemaOptional> => new SchemaOptional(D.optionalNullable(schema.decoder), E.optionalNullable(schema.encoder)); @@ -170,8 +169,7 @@ const stringLiteral = (str: T): Schema => }) ); -const stringEnum = (strs: T): Schema => - new Schema(D.stringEnum(strs), E.stringEnum(strs)); +const stringEnum = (strs: T): Schema => new Schema(D.stringEnum(strs), E.stringEnum(strs)); const oneOf = (f: (v: V) => Schema, ss: Array>): Schema => new Schema( @@ -179,9 +177,7 @@ const oneOf = (f: (v: V) => Schema, ss: Array>): Schema => E.oneOf((v) => f(v).encoder) ); -const discriminatedUnion = []>( - vars: Variants -): Schema> => { +const discriminatedUnion = []>(vars: Variants): Schema> => { type Ty = Infer; const d: D.Decoder = D.oneOf(vars.map((v) => v.schema.decoder)); const e: E.Encoder = E.oneOf((v) => { @@ -228,15 +224,10 @@ type VariantDef = { }; const variant = (def: VariantDef): Variant => { - const pattern = filterMap(def, (_, v): string | undefined => (typeof v == "string" ? v : undefined)) as Record< - string, - string - >; + const pattern = filterMap(def, (_, v): string | undefined => (typeof v == "string" ? v : undefined)) as Record; if (Object.keys(pattern).length == 0) { - throw new Error( - "Invalid variant definition. No discriminant identified. Discriminant must be provided as a string" - ); + throw new Error("Invalid variant definition. No discriminant identified. Discriminant must be provided as a string"); } const schemaDef: SchemaDef = mapValues(def, (_, value) => @@ -250,8 +241,7 @@ const variant = (def: VariantDef): Varian }; // Schema for a stringified JSON representation. -const stringified = (inner: Schema): Schema => - new Schema(D.stringified(inner.decoder), E.stringified(inner.encoder)); +const stringified = (inner: Schema): Schema => new Schema(D.stringified(inner.decoder), E.stringified(inner.encoder)); const recursive = (f: (s: Schema) => Schema): Schema => { const baseEncoder: Encoder = new Encoder((_) => { diff --git a/src/libs/maybe.ts b/src/libs/maybe.ts index fd92e83..be464e5 100644 --- a/src/libs/maybe.ts +++ b/src/libs/maybe.ts @@ -14,17 +14,7 @@ Values can be extracted using `instsanceof` tests. x satisfies never; } */ -export { - type Maybe, - type Nullable, - type Infer, - CallableJust as Just, - CallableNothing as Nothing, - fromOptional, - fromNullable, - catMaybes, - mapMaybe -}; +export { type Maybe, type Nullable, type Infer, CallableJust as Just, CallableNothing as Nothing, fromOptional, fromNullable, catMaybes, mapMaybe }; import Callable from "@/libs/callable"; diff --git a/src/libs/mvar.ts b/src/libs/mvar.ts new file mode 100644 index 0000000..c6af01b --- /dev/null +++ b/src/libs/mvar.ts @@ -0,0 +1,233 @@ +export { MVar }; + +import { MQueue } from "@/libs/queue"; +import { Maybe, Just, Nothing } from "@/libs/maybe"; + +type Value = + | { tag: "empty" } // + | { tag: "full"; value: T } + | { tag: "in_use" }; + +/** + * A mutable variable that is either empty or holds one value of type `A`. + * + * Coordinates concurrent operations via FIFO fairness: blocked `put`/`take`/`modify` + * calls resume in the order they were enqueued, so no waiter is starved. + * + * Reach for `MVar` when you need: + * - a completion/error latch (`MVar>`) + * - a single-slot mailbox between producer and consumer + * - atomic mutation of shared state without an external lock (`modify`) + */ +class MVar { + // Queued writes and modifications are resolved in the order in which they arrive. + private waitingEmpty: MQueue<[A, () => void]> = MQueue.new(); + private waitingFull: MQueue<(v: A) => Promise> = MQueue.new(); + private value: Value; + + private constructor(initial: Maybe) { + this.value = initial.maybe>({ tag: "empty" }, (value) => ({ tag: "full", value })); + } + + /** Create a full `MVar` holding `v`. The first `take` returns immediately; the first `put` blocks until taken. */ + static new(v: A): MVar { + return new MVar(Just(v)); + } + + /** Create an empty `MVar`. The first `put` returns immediately; the first `take` blocks until populated. */ + static newEmpty(): MVar { + return new MVar(Nothing()); + } + + private _unblockWaitingFull(x: A): void { + const r = this.waitingFull.dequeue(); + if (r instanceof Just) { + this.value = { tag: "in_use" }; + r.value(x); // we trigger the promise, but don't wait. + } else { + this.value = { tag: "full", value: x }; + } + } + + /** + * Place `v` into the MVar. Resolves once stored. + * If full or in use, blocks (FIFO) until empty. + */ + put(v: A): Promise { + const enqueue = () => + new Promise((resolve) => { + this.waitingEmpty.enqueue([ + v, + () => { + this._unblockWaitingFull(v); + resolve(); + } + ]); + }); + + switch (this.value.tag) { + case "empty": + this._unblockWaitingFull(v); + return Promise.resolve(); + case "full": + return enqueue(); + case "in_use": + return enqueue(); + default: + return this.value satisfies never; + } + } + + /** + * Non-blocking `put`. Returns `true` if stored, `false` if the MVar + * was full/in-use (and the value was discarded). + */ + tryPut(v: A): boolean { + switch (this.value.tag) { + case "empty": + this._unblockWaitingFull(v); + return true; + case "full": + return false; + case "in_use": + return false; + default: + return this.value satisfies never; + } + } + + private _unblockWaitingEmpty(): void { + this.value = { tag: "in_use" }; + // is there something waiting for it to be emtpy? + const r = this.waitingEmpty.dequeue(); + + if (r instanceof Nothing) { + // nope, let's just mark it as empty + this.value = { tag: "empty" }; + } else { + // hand off to the next pending writer; its trigger closure + // takes care of placing the value and resolving the put. + const [, trigger] = r.value; + trigger(); + } + } + + /** + * Remove and return the held value, leaving the MVar empty. + * If empty or in use, blocks (FIFO) until a value is put. + */ + take(): Promise { + const enqueue = () => + new Promise((resolve) => { + this.waitingFull.enqueue((v) => { + this._unblockWaitingEmpty(); + resolve(v); + return Promise.resolve(); + }); + }); + + switch (this.value.tag) { + case "empty": + return enqueue(); + case "full": { + const value = this.value.value; + this._unblockWaitingEmpty(); + return Promise.resolve(value); + } + case "in_use": + return enqueue(); + default: + return this.value satisfies never; + } + } + + /** + * Non-blocking `take`. Returns `Just(v)` if a value was taken, + * `Nothing()` if the MVar was empty/in-use. + */ + tryTake(): Maybe { + switch (this.value.tag) { + case "empty": + return Nothing(); + case "full": { + const value = this.value.value; + this._unblockWaitingEmpty(); + return Just(value); + } + case "in_use": + return Nothing(); + default: + return this.value satisfies never; + } + } + + /** + * Atomically transform the held value. `f` receives the current value + * and returns `[newValue, result]`. The MVar is marked in-use during the + * call, blocking other consumers. + * + * If `f` rejects, the original value is restored — safe under failure. + */ + async modify(f: (v: A) => Promise<[A, B]>): Promise { + const resume = async (value: A): Promise => { + try { + this.value = { tag: "in_use" }; + const [v, r] = await f(value); + this._unblockWaitingFull(v); + return r; + } catch (e) { + this._unblockWaitingFull(value); // put value back + return Promise.reject(e); + } + }; + + const enqueue = (): Promise => new Promise((resolve, reject) => this.waitingFull.enqueue((value) => resume(value).then(resolve).catch(reject))); + + switch (this.value.tag) { + case "empty": + return enqueue(); + case "full": + return resume(this.value.value); + case "in_use": + return enqueue(); + default: + return this.value satisfies never; + } + } + + /** Like `modify`, but `f` returns only the new value; no result is computed. */ + async modify_(f: (v: A) => Promise): Promise { + return this.modify((v) => f(v).then((x) => [x, undefined])); + } + + /** + * Read the held value without removing it. + * If empty or in use, blocks (FIFO) until a value is put. + */ + async read(): Promise { + const v = this.tryRead(); + if (v instanceof Just) { + return v.value; + } + return await this.modify(async (v) => [v, v]); + } + + /** + * Non-blocking `read`. Returns `Just(v)` if a value is held, + * `Nothing()` if the MVar is empty/in-use. + */ + tryRead(): Maybe { + switch (this.value.tag) { + case "empty": + return Nothing(); + case "full": { + const value = this.value.value; + return Just(value); + } + case "in_use": + return Nothing(); + default: + return this.value satisfies never; + } + } +} diff --git a/src/libs/queue.ts b/src/libs/queue.ts new file mode 100644 index 0000000..7775f60 --- /dev/null +++ b/src/libs/queue.ts @@ -0,0 +1,125 @@ +export { Queue, MQueue }; + +import { List } from "@/libs/list"; +import { Maybe, Just, Nothing } from "@/libs/maybe"; + +/** + * Immutable queue with amortised O(1) enqueue and dequeue. + * + * Implemented as two singly-linked lists: a `front` list for dequeues + * and a `back` list for enqueues. When the front empties, the back is + * reversed in place to become the new front — at most once per element + * across the queue's lifetime, giving amortised constant time. + * + * Use `Queue` for persistent queues that can be safely shared across + * async boundaries. For in-place mutation, prefer `MQueue`. + */ +class Queue { + private constructor( + public readonly length: number, + private front: List, + private back: List + ) {} + + /** Create an empty queue. */ + static new(): Queue { + return new Queue(0, List.empty(), List.empty()); + } + + /** Create a queue containing the elements of `xs`, preserving order. */ + static fromArray(xs: T[]): Queue { + let queue = Queue.new(); + for (const x of xs) { + queue = queue.enqueue(x); + } + return queue; + } + + /** True when the queue contains no elements. */ + isEmpty(): boolean { + return this.length == 0; + } + + /** Return the queue's elements as an array, front to back. */ + toArray(): T[] { + return this.front.toArray().concat(this.back.toArray().reverse()); + } + + /** Return a new queue with `v` added to the back. */ + enqueue(v: T): Queue { + return new Queue(this.length + 1, this.front, List.cons(v, this.back)); + } + + /** + * Remove the front element. Returns `Just([v, rest])` with the front + * value and the remaining queue, or `Nothing()` if empty. + */ + dequeue(): Maybe<[T, Queue]> { + // Here, if the front of the queue is empty we reverse + // the back and make it the front. This is how we achieve + // amortised O(1) time. + const reverseBack = (): Maybe<[T, Queue]> => { + const front = this.back.reverse(); + if (front.isEmpty()) return Nothing(); + const queue = new Queue(this.length, front, List.empty()); + return queue.dequeue(); + }; + + return this.front.head().unwrap(reverseBack, (v) => Just([v, new Queue(this.length - 1, this.front.tail(), this.back)])); + } +} + +/** + * Mutable wrapper around `Queue` with amortised O(1) enqueue and dequeue. + * + * Used internally by `MVar` to track waiters. Reach for it directly when a + * local producer/consumer needs in-place mutation without threading a new + * queue value through every call site. + */ +class MQueue { + private constructor(private queue: Queue) {} + + /** Create an empty mutable queue. */ + static new(): MQueue { + return new MQueue(Queue.new()); + } + + /** Create a mutable queue containing the elements of `xs`, preserving order. */ + static fromArray(xs: T[]): MQueue { + return new MQueue(Queue.fromArray(xs)); + } + + /** True when the queue contains no elements. */ + isEmpty(): boolean { + return this.queue.isEmpty(); + } + + /** Return the queue's elements as an array, front to back. */ + toArray(): T[] { + return this.queue.toArray(); + } + + /** Number of elements currently in the queue. */ + get length(): number { + return this.queue.length; + } + + /** Add `v` to the back of the queue. */ + enqueue(v: T): void { + this.queue = this.queue.enqueue(v); + } + + /** + * Remove and return the front element. Returns `Just(v)` if a value + * was removed, `Nothing()` if the queue was empty. + */ + dequeue(): Maybe { + return this.queue.dequeue().unwrap( + () => Nothing(), + ([v, queue]) => { + this.queue = queue; + return Just(v); + } + ); + } +} diff --git a/src/libs/remote-data.ts b/src/libs/remote-data.ts index ff875df..4e17e0a 100644 --- a/src/libs/remote-data.ts +++ b/src/libs/remote-data.ts @@ -1,10 +1,4 @@ -export { - type RemoteData, - CallableSuccess as Ready, - CallableFailure as Failed, - CallableNotAsked as NotAsked, - CallableLoading as Loading -}; +export { type RemoteData, CallableSuccess as Ready, CallableFailure as Failed, CallableNotAsked as NotAsked, CallableLoading as Loading }; import { Nullable, Maybe, Nothing, Just } from "@/libs/maybe"; import Callable from "@/libs/callable"; diff --git a/src/libs/router.ts b/src/libs/router.ts index 22088ec..6e1e6b6 100644 --- a/src/libs/router.ts +++ b/src/libs/router.ts @@ -104,35 +104,16 @@ type Response = Render | JSON | Redirect | SSE; // Convenience constructors for responses. -const json = ({ - status = 200, - headers = {}, - content -}: { - status?: number; - headers?: Headers; - content: Json; -}): Response => new JSON({ status, headers, content }); +const json = ({ status = 200, headers = {}, content }: { status?: number; headers?: Headers; content: Json }): Response => + new JSON({ status, headers, content }); const redirect = (path: string): Response => new Redirect({ path }); -const render = ({ - status = 200, - headers = {}, - content -}: { - status?: number; - headers?: Headers; - content: string | Json; -}): Response => new Render({ status, headers, content }); - -const sse = ({ - headers = {}, - stream -}: { - headers?: Headers; - stream: (emit: SendMessage, onError: OnError) => Future; -}): Response => new SSE({ headers, stream }); +const render = ({ status = 200, headers = {}, content }: { status?: number; headers?: Headers; content: string | Json }): Response => + new Render({ status, headers, content }); + +const sse = ({ headers = {}, stream }: { headers?: Headers; stream: (emit: SendMessage, onError: OnError) => Future }): Response => + new SSE({ headers, stream }); // A middleware is something that transforms the environment. type Middleware = (req: express.Request, env: A) => Future; @@ -169,10 +150,7 @@ function send(response: Response, res: express.Response): void { } } -function streamSSEResponse( - res: express.Response, - stream: (emit: SendMessage, onError: OnError) => Future -): void { +function streamSSEResponse(res: express.Response, stream: (emit: SendMessage, onError: OnError) => Future): void { let connectionClosed = false; let endStream: Cancel = () => {}; const closeConnection = () => { diff --git a/src/libs/time.ts b/src/libs/time.ts index 5246d99..d6fbf00 100644 --- a/src/libs/time.ts +++ b/src/libs/time.ts @@ -357,10 +357,7 @@ class Duration { // Chained factory methods (seconds → milliseconds) accumulate // IEEE-754 floating point errors (~10⁻¹⁵). Rounding to milliseconds is safe // as it's our internal precision and the error is far below this threshold. - const total = Duration.days(days) - .add(Duration.hours(hours)) - .add(Duration.minutes(minutes)) - .add(Duration.seconds(seconds)); + const total = Duration.days(days).add(Duration.hours(hours)).add(Duration.minutes(minutes)).add(Duration.seconds(seconds)); const millis = Math.round(total.asMilliseconds()); return Just(Duration.milliseconds(negative ? -millis : millis)); diff --git a/src/libs/trampoline.ts b/src/libs/trampoline.ts index 7ef66f4..b752556 100644 --- a/src/libs/trampoline.ts +++ b/src/libs/trampoline.ts @@ -44,9 +44,7 @@ class Rec { type Fun = (...args: A) => B; -function fix( - f: Fun<[Fun>, (r: R) => Trampoline], Fun>> -): Fun { +function fix(f: Fun<[Fun>, (r: R) => Trampoline], Fun>>): Fun { let lazy_f: Fun> = (..._: A) => { throw new Error("recursion error"); }; diff --git a/src/libs/types.ts b/src/libs/types.ts index aafa799..537c476 100644 --- a/src/libs/types.ts +++ b/src/libs/types.ts @@ -31,8 +31,7 @@ type IsUnion = [T] extends [UnionToIntersection] ? false : true; // // UnionToTuple == [ A , B ] // -type UnionToTuple = - IsUnion extends true ? UnionToTuple>, [PopUnion, ...A]> : [T, ...A]; +type UnionToTuple = IsUnion extends true ? UnionToTuple>, [PopUnion, ...A]> : [T, ...A]; // Pick an option from a union // diff --git a/tsconfig.json b/tsconfig.json index 58bb089..6573bd2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,7 +25,7 @@ "allowUnusedLabels": false, "allowUnreachableCode": false, "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*", "./*"] } }, "include": ["index.ts", "src/**/*"],