Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b3d027e3b | |||
| ce209e22a2 | |||
| 56fa267e09 | |||
| 2b73a08916 | |||
| b0190116a7 | |||
| 11c0ad24aa | |||
| 4cfe8a8bf8 | |||
| 754951bbbd | |||
| 38d4d03ba8 | |||
| 162da4bd97 | |||
| 18563363da | |||
| d18891523d | |||
| bda7a488d4 | |||
| bde80e24e5 | |||
| 0114a3f317 | |||
| a4d4a3f545 | |||
| e8b4eb8972 | |||
| 7c18b95826 | |||
| 381afd6e10 | |||
| a8d5b14d1a | |||
| 39b4f861f3 | |||
| 13f89d5aa5 | |||
| f29cb81c7d | |||
| d034a61635 | |||
| 2eca96c80f | |||
| 057208e022 | |||
| 6db0f9855c | |||
| e0894d7b63 | |||
| cd3a58a4c2 | |||
| a5614f988f | |||
| 0f58efe030 | |||
| 4abb464345 | |||
| d1d3d420bf | |||
| 1d68cd288c | |||
| 0b8f8bc196 | |||
| 4d30ad1e7c | |||
| c90640e0e1 | |||
| 36b51cad33 | |||
| 776e61d1ec | |||
| 28aebb2772 | |||
| 6494f48136 | |||
| 15fae6cb60 | |||
| aacf1d20d3 | |||
| bcf7817127 | |||
| abf79ae24c | |||
| 922633ea9d | |||
| 49b40e3c90 | |||
| df3276fc87 | |||
| f8f986536b | |||
| 785635caef | |||
| ec27518eca | |||
| 8ee4ada38e | |||
| ab7b1d78bf | |||
| 2f44d1900e | |||
| cb535eef9d | |||
| d3ec6f75f4 | |||
| 9a8b2ae0b1 | |||
| eadb0e25da | |||
| ddd30ef304 | |||
| 2abf1100ee | |||
| bd2e34f3bd | |||
| a45c3a0049 | |||
| 52d1ee70a0 | |||
| 0a9fcab56f | |||
| 62fae6d182 | |||
| 3a5be7ad33 | |||
| f1e88d35ba | |||
| b737e87d9a | |||
| bd6e81f30b | |||
| f080147363 | |||
| 0051b605ae | |||
| 56e0e5ce65 | |||
| d065d5a8ec | |||
| cf79208055 | |||
| f276a8db42 |
@@ -0,0 +1,27 @@
|
||||
"on":
|
||||
push:
|
||||
branches:
|
||||
- opencode-remote-voice
|
||||
name: Deploy to apn-relay
|
||||
jobs:
|
||||
porter-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Set Github tag
|
||||
id: vars
|
||||
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
- name: Setup porter
|
||||
uses: porter-dev/setup-porter@v0.1.0
|
||||
- name: Deploy stack
|
||||
timeout-minutes: 30
|
||||
run: porter apply
|
||||
env:
|
||||
PORTER_APP_NAME: apn-relay
|
||||
PORTER_CLUSTER: "5534"
|
||||
PORTER_DEPLOYMENT_TARGET_ID: d60e67f5-b0a6-4275-8ed6-3cebaf092147
|
||||
PORTER_HOST: https://dashboard.porter.run
|
||||
PORTER_PROJECT: "18525"
|
||||
PORTER_TAG: ${{ steps.vars.outputs.sha_short }}
|
||||
PORTER_TOKEN: ${{ secrets.PORTER_APP_18525_975734319 }}
|
||||
@@ -1,12 +1,8 @@
|
||||
- To regenerate the JavaScript SDK, run `./packages/sdk/js/script/build.ts`.
|
||||
- ALWAYS USE PARALLEL TOOLS WHEN APPLICABLE.
|
||||
- The default branch in this repo is `dev`.
|
||||
- Local `main` ref may not exist; use `dev` or `origin/dev` for diffs.
|
||||
- Prefer automation: execute requested actions without confirmation unless blocked by missing info or safety/irreversibility.
|
||||
# OpenCode Monorepo Agent Guide
|
||||
|
||||
## Style Guide
|
||||
This file is for coding agents working in `/Users/ryanvogel/dev/opencode`.
|
||||
|
||||
### General Principles
|
||||
## Scope And Precedence
|
||||
|
||||
- Keep things in one function unless composable or reusable
|
||||
- Avoid `try`/`catch` where possible
|
||||
@@ -56,48 +52,48 @@ else foo = 2
|
||||
|
||||
### Control Flow
|
||||
|
||||
Avoid `else` statements. Prefer early returns.
|
||||
- Prefer early returns over nested `else` blocks.
|
||||
- Keep functions focused; split only when it improves reuse or readability.
|
||||
|
||||
```ts
|
||||
// Good
|
||||
function foo() {
|
||||
if (condition) return 1
|
||||
return 2
|
||||
}
|
||||
### Error Handling
|
||||
|
||||
// Bad
|
||||
function foo() {
|
||||
if (condition) return 1
|
||||
else return 2
|
||||
}
|
||||
```
|
||||
- Fail with actionable messages.
|
||||
- Avoid swallowing errors silently.
|
||||
- Log enough context to debug production issues (IDs, env, status), but never secrets.
|
||||
- In UI code, degrade gracefully for missing capabilities.
|
||||
|
||||
### Schema Definitions (Drizzle)
|
||||
### Data / DB
|
||||
|
||||
Use snake_case for field names so column names don't need to be redefined as strings.
|
||||
- For Drizzle schema, use snake_case fields and columns.
|
||||
- Keep migration and schema changes minimal and explicit.
|
||||
- Follow package-specific DB guidance in `packages/opencode/AGENTS.md`.
|
||||
|
||||
```ts
|
||||
// Good
|
||||
const table = sqliteTable("session", {
|
||||
id: text().primaryKey(),
|
||||
project_id: text().notNull(),
|
||||
created_at: integer().notNull(),
|
||||
})
|
||||
### Testing Philosophy
|
||||
|
||||
// Bad
|
||||
const table = sqliteTable("session", {
|
||||
id: text("id").primaryKey(),
|
||||
projectID: text("project_id").notNull(),
|
||||
createdAt: integer("created_at").notNull(),
|
||||
})
|
||||
```
|
||||
- Prefer testing real behavior over mocks.
|
||||
- Add regression tests for bug fixes where practical.
|
||||
- Keep fixtures small and focused.
|
||||
|
||||
## Testing
|
||||
## Agent Workflow Tips
|
||||
|
||||
- Avoid mocks as much as possible
|
||||
- Test actual implementation, do not duplicate logic into tests
|
||||
- Tests cannot run from repo root (guard: `do-not-run-tests-from-root`); run from package dirs like `packages/opencode`.
|
||||
- Read existing code paths before introducing new abstractions.
|
||||
- Match local patterns first; do not impose a new style per file.
|
||||
- If a package has its own `AGENTS.md`, review it before editing.
|
||||
- For OpenCode Effect services, follow `packages/opencode/AGENTS.md` strictly.
|
||||
|
||||
## Type Checking
|
||||
## Known Operational Notes
|
||||
|
||||
- Always run `bun typecheck` from package directories (e.g., `packages/opencode`), never `tsc` directly.
|
||||
- `packages/app/AGENTS.md` says: never restart app/server processes during that package's debugging workflow.
|
||||
- `packages/app/AGENTS.md` also documents local backend+web split for UI work.
|
||||
- `packages/opencode/AGENTS.md` contains mandatory Effect and database conventions.
|
||||
|
||||
## Regeneration / Special Scripts
|
||||
|
||||
- Regenerate JS SDK with: `./packages/sdk/js/script/build.ts`
|
||||
|
||||
## Quick Checklist Before Finishing
|
||||
|
||||
- Ran relevant package checks.
|
||||
- Updated docs/config when behavior changed.
|
||||
- Avoided committing unrelated files.
|
||||
- Kept edits minimal and aligned with local conventions.
|
||||
|
||||
@@ -27,6 +27,24 @@
|
||||
"turbo": "2.8.13",
|
||||
},
|
||||
},
|
||||
"packages/apn-relay": {
|
||||
"name": "@opencode-ai/apn-relay",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@planetscale/database": "1.19.0",
|
||||
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
||||
"hono": "4.10.7",
|
||||
"jose": "6.0.11",
|
||||
"zod": "4.1.8",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@types/bun": "1.3.11",
|
||||
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
||||
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
||||
"typescript": "5.8.2",
|
||||
},
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "1.14.18",
|
||||
@@ -312,6 +330,58 @@
|
||||
"typescript": "catalog:",
|
||||
},
|
||||
},
|
||||
"packages/mobile-voice": {
|
||||
"name": "mobile-voice",
|
||||
"version": "1.0.2",
|
||||
"dependencies": {
|
||||
"@fugood/react-native-audio-pcm-stream": "1.1.4",
|
||||
"@react-navigation/bottom-tabs": "^7.15.5",
|
||||
"@react-navigation/elements": "^2.9.10",
|
||||
"@react-navigation/native": "^7.1.33",
|
||||
"expo": "~55.0.9",
|
||||
"expo-asset": "~55.0.10",
|
||||
"expo-audio": "~55.0.9",
|
||||
"expo-camera": "~55.0.11",
|
||||
"expo-constants": "~55.0.9",
|
||||
"expo-dev-client": "~55.0.19",
|
||||
"expo-device": "~55.0.10",
|
||||
"expo-file-system": "~55.0.12",
|
||||
"expo-font": "~55.0.4",
|
||||
"expo-glass-effect": "~55.0.8",
|
||||
"expo-haptics": "~55.0.9",
|
||||
"expo-image": "~55.0.6",
|
||||
"expo-linking": "~55.0.9",
|
||||
"expo-notifications": "~55.0.14",
|
||||
"expo-router": "~55.0.8",
|
||||
"expo-splash-screen": "~55.0.13",
|
||||
"expo-status-bar": "~55.0.4",
|
||||
"expo-symbols": "~55.0.5",
|
||||
"expo-system-ui": "~55.0.11",
|
||||
"expo-task-manager": "~55.0.10",
|
||||
"expo-updates": "~55.0.16",
|
||||
"expo-web-browser": "~55.0.10",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-native": "0.83.4",
|
||||
"react-native-audio-api": "^0.11.7",
|
||||
"react-native-gesture-handler": "~2.30.0",
|
||||
"react-native-reanimated": "4.2.1",
|
||||
"react-native-safe-area-context": "~5.6.2",
|
||||
"react-native-screens": "~4.23.0",
|
||||
"react-native-web": "~0.21.0",
|
||||
"react-native-worklets": "0.7.2",
|
||||
"react-native-zeroconf": "0.14.0",
|
||||
"whisper.rn": "0.5.5",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "~19.2.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
||||
"@typescript-eslint/parser": "^8.57.2",
|
||||
"babel-preset-expo": "~55.0.8",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"typescript": "~5.9.2",
|
||||
},
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "opencode",
|
||||
"version": "1.14.18",
|
||||
@@ -404,6 +474,7 @@
|
||||
"opencode-poe-auth": "0.0.1",
|
||||
"opentui-spinner": "0.0.6",
|
||||
"partial-json": "0.1.7",
|
||||
"qrcode": "1.5.4",
|
||||
"remeda": "catalog:",
|
||||
"semver": "^7.6.3",
|
||||
"solid-js": "catalog:",
|
||||
@@ -443,6 +514,7 @@
|
||||
"@types/mime-types": "3.0.1",
|
||||
"@types/npm-package-arg": "6.1.4",
|
||||
"@types/npmcli__arborist": "6.3.3",
|
||||
"@types/qrcode": "1.5.5",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/turndown": "5.0.5",
|
||||
"@types/which": "3.0.4",
|
||||
@@ -966,6 +1038,10 @@
|
||||
|
||||
"@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow=="],
|
||||
|
||||
"@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="],
|
||||
|
||||
"@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.8", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||
|
||||
"@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="],
|
||||
@@ -978,6 +1054,8 @@
|
||||
|
||||
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
|
||||
|
||||
"@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA=="],
|
||||
|
||||
"@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.28.6", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg=="],
|
||||
|
||||
"@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="],
|
||||
@@ -988,24 +1066,138 @@
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
|
||||
|
||||
"@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.29.0", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-decorators": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA=="],
|
||||
|
||||
"@babel/plugin-proposal-export-default-from": ["@babel/plugin-proposal-export-default-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw=="],
|
||||
|
||||
"@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="],
|
||||
|
||||
"@babel/plugin-syntax-bigint": ["@babel/plugin-syntax-bigint@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg=="],
|
||||
|
||||
"@babel/plugin-syntax-class-properties": ["@babel/plugin-syntax-class-properties@7.12.13", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA=="],
|
||||
|
||||
"@babel/plugin-syntax-class-static-block": ["@babel/plugin-syntax-class-static-block@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw=="],
|
||||
|
||||
"@babel/plugin-syntax-decorators": ["@babel/plugin-syntax-decorators@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA=="],
|
||||
|
||||
"@babel/plugin-syntax-dynamic-import": ["@babel/plugin-syntax-dynamic-import@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ=="],
|
||||
|
||||
"@babel/plugin-syntax-export-default-from": ["@babel/plugin-syntax-export-default-from@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Svlx1fjJFnNz0LZeUaybRukSxZI3KkpApUmIRzEdXC5k8ErTOz0OD0kNrICi5Vc3GlpP5ZCeRyRO+mfWTSz+iQ=="],
|
||||
|
||||
"@babel/plugin-syntax-flow": ["@babel/plugin-syntax-flow@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew=="],
|
||||
|
||||
"@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw=="],
|
||||
|
||||
"@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="],
|
||||
|
||||
"@babel/plugin-syntax-json-strings": ["@babel/plugin-syntax-json-strings@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA=="],
|
||||
|
||||
"@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="],
|
||||
|
||||
"@babel/plugin-syntax-logical-assignment-operators": ["@babel/plugin-syntax-logical-assignment-operators@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig=="],
|
||||
|
||||
"@babel/plugin-syntax-nullish-coalescing-operator": ["@babel/plugin-syntax-nullish-coalescing-operator@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ=="],
|
||||
|
||||
"@babel/plugin-syntax-numeric-separator": ["@babel/plugin-syntax-numeric-separator@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug=="],
|
||||
|
||||
"@babel/plugin-syntax-object-rest-spread": ["@babel/plugin-syntax-object-rest-spread@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA=="],
|
||||
|
||||
"@babel/plugin-syntax-optional-catch-binding": ["@babel/plugin-syntax-optional-catch-binding@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q=="],
|
||||
|
||||
"@babel/plugin-syntax-optional-chaining": ["@babel/plugin-syntax-optional-chaining@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg=="],
|
||||
|
||||
"@babel/plugin-syntax-private-property-in-object": ["@babel/plugin-syntax-private-property-in-object@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg=="],
|
||||
|
||||
"@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="],
|
||||
|
||||
"@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="],
|
||||
|
||||
"@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="],
|
||||
|
||||
"@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.29.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", "@babel/traverse": "^7.29.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w=="],
|
||||
|
||||
"@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g=="],
|
||||
|
||||
"@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw=="],
|
||||
|
||||
"@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.27.1", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA=="],
|
||||
|
||||
"@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ=="],
|
||||
|
||||
"@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.28.4", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/traverse": "^7.28.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA=="],
|
||||
|
||||
"@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/template": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ=="],
|
||||
|
||||
"@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw=="],
|
||||
|
||||
"@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ=="],
|
||||
|
||||
"@babel/plugin-transform-flow-strip-types": ["@babel/plugin-transform-flow-strip-types@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-flow": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg=="],
|
||||
|
||||
"@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw=="],
|
||||
|
||||
"@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.27.1", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ=="],
|
||||
|
||||
"@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA=="],
|
||||
|
||||
"@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A=="],
|
||||
|
||||
"@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="],
|
||||
|
||||
"@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.29.0", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ=="],
|
||||
|
||||
"@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA=="],
|
||||
|
||||
"@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w=="],
|
||||
|
||||
"@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.28.6", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA=="],
|
||||
|
||||
"@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ=="],
|
||||
|
||||
"@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg=="],
|
||||
|
||||
"@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.27.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg=="],
|
||||
|
||||
"@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg=="],
|
||||
|
||||
"@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA=="],
|
||||
|
||||
"@babel/plugin-transform-react-display-name": ["@babel/plugin-transform-react-display-name@7.28.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-jsx": "^7.28.6", "@babel/types": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.27.1", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
|
||||
|
||||
"@babel/plugin-transform-react-pure-annotations": ["@babel/plugin-transform-react-pure-annotations@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA=="],
|
||||
|
||||
"@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.29.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog=="],
|
||||
|
||||
"@babel/plugin-transform-runtime": ["@babel/plugin-transform-runtime@7.29.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w=="],
|
||||
|
||||
"@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ=="],
|
||||
|
||||
"@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA=="],
|
||||
|
||||
"@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g=="],
|
||||
|
||||
"@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg=="],
|
||||
|
||||
"@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="],
|
||||
|
||||
"@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw=="],
|
||||
|
||||
"@babel/preset-react": ["@babel/preset-react@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-transform-react-display-name": "^7.28.0", "@babel/plugin-transform-react-jsx": "^7.27.1", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ=="],
|
||||
|
||||
"@babel/preset-typescript": ["@babel/preset-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ=="],
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
|
||||
@@ -1066,6 +1258,8 @@
|
||||
|
||||
"@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.48", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.20.0" }, "peerDependencies": { "effect": "^4.0.0-beta.48" } }, "sha512-wlhcdDHyacydCgiWdM8JwtQkViQhZsC8uJZ9wMoZXYxlCTvqfdzLeWw4A1UVMoq7sS6/KR1aZVeFkUjrqonncQ=="],
|
||||
|
||||
"@egjs/hammerjs": ["@egjs/hammerjs@2.0.17", "", { "dependencies": { "@types/hammerjs": "^2.0.36" } }, "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A=="],
|
||||
|
||||
"@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="],
|
||||
|
||||
"@electron/fuses": ["@electron/fuses@1.8.0", "", { "dependencies": { "chalk": "^4.1.1", "fs-extra": "^9.0.1", "minimist": "^1.2.5" }, "bin": { "electron-fuses": "dist/bin.js" } }, "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw=="],
|
||||
@@ -1158,6 +1352,82 @@
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
|
||||
|
||||
"@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="],
|
||||
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.5.5", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w=="],
|
||||
|
||||
"@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="],
|
||||
|
||||
"@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="],
|
||||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.1", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ=="],
|
||||
|
||||
"@expo-google-fonts/material-symbols": ["@expo-google-fonts/material-symbols@0.4.32", "", {}, "sha512-+RtX6GNteOQEycWKcliKtHYQ3NvFamuD4ZCb7MLwzImkZwxRXjUO1kSVV5FFB2koK5WNYwUCye2KkDp8LFD1SQ=="],
|
||||
|
||||
"@expo/cli": ["@expo/cli@55.0.24", "", { "dependencies": { "@expo/code-signing-certificates": "^0.0.6", "@expo/config": "~55.0.15", "@expo/config-plugins": "~55.0.8", "@expo/devcert": "^1.2.1", "@expo/env": "~2.1.1", "@expo/image-utils": "^0.8.13", "@expo/json-file": "^10.0.13", "@expo/log-box": "55.0.10", "@expo/metro": "~55.0.0", "@expo/metro-config": "~55.0.16", "@expo/osascript": "^2.4.2", "@expo/package-manager": "^1.10.4", "@expo/plist": "^0.5.2", "@expo/prebuild-config": "^55.0.15", "@expo/require-utils": "^55.0.4", "@expo/router-server": "^55.0.14", "@expo/schema-utils": "^55.0.3", "@expo/spawn-async": "^1.7.2", "@expo/ws-tunnel": "^1.0.1", "@expo/xcpretty": "^4.4.0", "@react-native/dev-middleware": "0.83.4", "accepts": "^1.3.8", "arg": "^5.0.2", "better-opn": "~3.0.2", "bplist-creator": "0.1.0", "bplist-parser": "^0.3.1", "chalk": "^4.0.0", "ci-info": "^3.3.0", "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "dnssd-advertise": "^1.1.4", "expo-server": "^55.0.7", "fetch-nodeshim": "^0.4.10", "getenv": "^2.0.0", "glob": "^13.0.0", "lan-network": "^0.2.1", "multitars": "^0.2.3", "node-forge": "^1.3.3", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^4.0.3", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "resolve-from": "^5.0.0", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "source-map-support": "~0.5.21", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "terminal-link": "^2.1.1", "toqr": "^0.1.1", "wrap-ansi": "^7.0.0", "ws": "^8.12.1", "zod": "^3.25.76" }, "peerDependencies": { "expo": "*", "expo-router": "*", "react-native": "*" }, "optionalPeers": ["expo-router", "react-native"], "bin": { "expo-internal": "build/bin/cli" } }, "sha512-Z6Xh0WNTg1LvoZQ77zO3snF2cFiv1xf0VguDlwTL1Ql87oMOp30f7mjl9jeaSHqoWkgiAbmxgCKKIGjVX/keiA=="],
|
||||
|
||||
"@expo/code-signing-certificates": ["@expo/code-signing-certificates@0.0.6", "", { "dependencies": { "node-forge": "^1.3.3" } }, "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w=="],
|
||||
|
||||
"@expo/config": ["@expo/config@55.0.15", "", { "dependencies": { "@expo/config-plugins": "~55.0.8", "@expo/config-types": "^55.0.5", "@expo/json-file": "^10.0.13", "@expo/require-utils": "^55.0.4", "deepmerge": "^4.3.1", "getenv": "^2.0.0", "glob": "^13.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4" } }, "sha512-lHc0ELIQ8126jYOMZpLv3WIuvordW98jFg5aT/J1/12n2ycuXu01XLZkJsdw0avO34cusUYb1It+MvY8JiMduA=="],
|
||||
|
||||
"@expo/config-plugins": ["@expo/config-plugins@55.0.8", "", { "dependencies": { "@expo/config-types": "^55.0.5", "@expo/json-file": "~10.0.13", "@expo/plist": "^0.5.2", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^13.0.0", "resolve-from": "^5.0.0", "semver": "^7.5.4", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-8WfWTRntTCcowfOS+tHdB0z98gKetTwktg4G5TWkCkXVa8Jt1NUnvzaaU4UHk2vbR2U4N84RyZJFizSwfF6C9g=="],
|
||||
|
||||
"@expo/config-types": ["@expo/config-types@55.0.5", "", {}, "sha512-sCmSUZG4mZ/ySXvfyyBdhjivz8Q539X1NondwDdYG7s3SBsk+wsgPJzYsqgAG/P9+l0xWjUD2F+kQ1cAJ6NNLg=="],
|
||||
|
||||
"@expo/devcert": ["@expo/devcert@1.2.1", "", { "dependencies": { "@expo/sudo-prompt": "^9.3.1", "debug": "^3.1.0" } }, "sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA=="],
|
||||
|
||||
"@expo/devtools": ["@expo/devtools@55.0.2", "", { "dependencies": { "chalk": "^4.1.2" }, "peerDependencies": { "react": "*", "react-native": "*" }, "optionalPeers": ["react", "react-native"] }, "sha512-4VsFn9MUriocyuhyA+ycJP3TJhUsOFHDc270l9h3LhNpXMf6wvIdGcA0QzXkZtORXmlDybWXRP2KT1k36HcQkA=="],
|
||||
|
||||
"@expo/dom-webview": ["@expo/dom-webview@55.0.5", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-lt3uxYOCk3wmWvtOOvsC35CKGbDAOx5C2EaY8SH1JVSfBzqmF8Cs0Xp1MPxncDPMyxpMiWx5SvvV/iLF1rJU4A=="],
|
||||
|
||||
"@expo/env": ["@expo/env@2.1.1", "", { "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", "getenv": "^2.0.0" } }, "sha512-rVvHC4I6xlPcg+mAO09ydUi2Wjv1ZytpLmHOSzvXzBAz9mMrJggqCe4s4dubjJvi/Ino/xQCLhbaLCnTtLpikg=="],
|
||||
|
||||
"@expo/fingerprint": ["@expo/fingerprint@0.16.6", "", { "dependencies": { "@expo/env": "^2.0.11", "@expo/spawn-async": "^1.7.2", "arg": "^5.0.2", "chalk": "^4.1.2", "debug": "^4.3.4", "getenv": "^2.0.0", "glob": "^13.0.0", "ignore": "^5.3.1", "minimatch": "^10.2.2", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "bin": { "fingerprint": "bin/cli.js" } }, "sha512-nRITNbnu3RKSHPvKVehrSU4KG2VY9V8nvULOHBw98ukHCAU4bGrU5APvcblOkX3JAap+xEHsg/mZvqlvkLInmQ=="],
|
||||
|
||||
"@expo/image-utils": ["@expo/image-utils@0.8.13", "", { "dependencies": { "@expo/require-utils": "^55.0.4", "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "getenv": "^2.0.0", "jimp-compact": "0.16.1", "parse-png": "^2.1.0", "semver": "^7.6.0" } }, "sha512-1I//yBQeTY6p0u1ihqGNDAr35EbSG8uFEupFrIF0jd++h9EWH33521yZJU1yE+mwGlzCb61g3ehu78siMhXBlA=="],
|
||||
|
||||
"@expo/json-file": ["@expo/json-file@10.0.13", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "json5": "^2.2.3" } }, "sha512-pX/XjQn7tgNw6zuuV2ikmegmwe/S7uiwhrs2wXrANMkq7ozrA+JcZwgW9Q/8WZgciBzfAhNp5hnackHcrmapQA=="],
|
||||
|
||||
"@expo/local-build-cache-provider": ["@expo/local-build-cache-provider@55.0.11", "", { "dependencies": { "@expo/config": "~55.0.15", "chalk": "^4.1.2" } }, "sha512-rJ4RTCrkeKaXaido/bVyhl90ZRtVTOEbj59F1PWVjIEIVgjdlfc1J3VD9v7hEsbf/+8Tbr/PgvWhT6Visi5sLQ=="],
|
||||
|
||||
"@expo/log-box": ["@expo/log-box@55.0.10", "", { "dependencies": { "@expo/dom-webview": "^55.0.5", "anser": "^1.4.9", "stacktrace-parser": "^0.1.10" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-7jdikExgIrCIF5e3P1qMwcUZ2tcxrNdVqE9Y8kNMUHqZ+ipMlin+SiZwJKHM1+am4CYGjhdyrzbnIpvEcLDYcg=="],
|
||||
|
||||
"@expo/metro": ["@expo/metro@55.0.0", "", { "dependencies": { "metro": "0.83.5", "metro-babel-transformer": "0.83.5", "metro-cache": "0.83.5", "metro-cache-key": "0.83.5", "metro-config": "0.83.5", "metro-core": "0.83.5", "metro-file-map": "0.83.5", "metro-minify-terser": "0.83.5", "metro-resolver": "0.83.5", "metro-runtime": "0.83.5", "metro-source-map": "0.83.5", "metro-symbolicate": "0.83.5", "metro-transform-plugins": "0.83.5", "metro-transform-worker": "0.83.5" } }, "sha512-wohGl+4y17rGHU+lq8UqC5neOXL/HOThorDYXTMbOcBL1jYwcK11MBc151gDMpjpgdVUzgHne0H5RfCIhIN4hA=="],
|
||||
|
||||
"@expo/metro-config": ["@expo/metro-config@55.0.16", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", "@expo/config": "~55.0.15", "@expo/env": "~2.1.1", "@expo/json-file": "~10.0.13", "@expo/metro": "~55.0.0", "@expo/spawn-async": "^1.7.2", "browserslist": "^4.25.0", "chalk": "^4.1.0", "debug": "^4.3.2", "getenv": "^2.0.0", "glob": "^13.0.0", "hermes-parser": "^0.32.0", "jsc-safe-url": "^0.2.4", "lightningcss": "^1.30.1", "picomatch": "^4.0.3", "postcss": "~8.4.32", "resolve-from": "^5.0.0" }, "peerDependencies": { "expo": "*" }, "optionalPeers": ["expo"] }, "sha512-JaWDw0dmYZ5pOqA+3/Efvl8JzCVgWQVPogHFjTRC5azUgAsFV+T7moOaZTSgg4d+5TjFZjZbMZg4SUomE7LiGg=="],
|
||||
|
||||
"@expo/metro-runtime": ["@expo/metro-runtime@55.0.9", "", { "dependencies": { "@expo/log-box": "55.0.10", "anser": "^1.4.9", "pretty-format": "^29.7.0", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-dom": "*", "react-native": "*" }, "optionalPeers": ["react-dom"] }, "sha512-H37b2Mc/8GiQbwtUFzUTxA3KsAMZu00SRg/RhbHa9xVE7J0n5ZX4NHy0LJEFAbkzTb1TUy1hLpo3oEKnG+rLyg=="],
|
||||
|
||||
"@expo/osascript": ["@expo/osascript@2.4.2", "", { "dependencies": { "@expo/spawn-async": "^1.7.2" } }, "sha512-/XP7PSYF2hzOZzqfjgkoWtllyeTN8dW3aM4P6YgKcmmPikKL5FdoyQhti4eh6RK5a5VrUXJTOlTNIpIHsfB5Iw=="],
|
||||
|
||||
"@expo/package-manager": ["@expo/package-manager@1.10.4", "", { "dependencies": { "@expo/json-file": "^10.0.13", "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "resolve-workspace-root": "^2.0.0" } }, "sha512-y9Mr4Kmpk4abAVZrNNPCdzOZr8nLLyi18p1SXr0RCVA8IfzqZX/eY4H+50a0HTmXqIsPZrQdcdb4I3ekMS9GvQ=="],
|
||||
|
||||
"@expo/plist": ["@expo/plist@0.5.2", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-o4xdVdBpe4aTl3sPMZ2u3fJH4iG1I768EIRk1xRZP+GaFI93MaR3JvoFibYqxeTmLQ1p1kNEVqylfUjezxx45g=="],
|
||||
|
||||
"@expo/prebuild-config": ["@expo/prebuild-config@55.0.15", "", { "dependencies": { "@expo/config": "~55.0.15", "@expo/config-plugins": "~55.0.8", "@expo/config-types": "^55.0.5", "@expo/image-utils": "^0.8.13", "@expo/json-file": "^10.0.13", "@react-native/normalize-colors": "0.83.4", "debug": "^4.3.1", "resolve-from": "^5.0.0", "semver": "^7.6.0", "xml2js": "0.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-UcCzVhVBE42UbY5U3t/q1Rk2fSFW/B50LJpB6oFpXhImJfvLKu7ayOFU9XcHd38K89i4GqSia/xXuxQvu4RUBg=="],
|
||||
|
||||
"@expo/require-utils": ["@expo/require-utils@55.0.4", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8" }, "peerDependencies": { "typescript": "^5.0.0 || ^5.0.0-0" }, "optionalPeers": ["typescript"] }, "sha512-JAANvXqV7MOysWeVWgaiDzikoyDjJWOV/ulOW60Zb3kXJfrx2oZOtGtDXDFKD1mXuahQgoM5QOjuZhF7gFRNjA=="],
|
||||
|
||||
"@expo/router-server": ["@expo/router-server@55.0.14", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "@expo/metro-runtime": "^55.0.9", "expo": "*", "expo-constants": "^55.0.13", "expo-font": "^55.0.6", "expo-router": "*", "expo-server": "^55.0.7", "react": "*", "react-dom": "*", "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" }, "optionalPeers": ["@expo/metro-runtime", "expo-router", "react-dom", "react-server-dom-webpack"] }, "sha512-YJjbeLMLp+ZjCnajHI+jEppNzXY372K0u4I4fLKGnA/loFX14aouDsg4tqZVGlZx6NUpnN8Bb3Tmw2BLTXT5Qw=="],
|
||||
|
||||
"@expo/schema-utils": ["@expo/schema-utils@55.0.3", "", {}, "sha512-l9KHVjTo6MvoeyvwNr6AjckGJm8NIcqZ3QSAh51cWozXW9v2AUjyCyqYtFtyntLWRZ0x/ByYJishpQo4ZQq45Q=="],
|
||||
|
||||
"@expo/sdk-runtime-versions": ["@expo/sdk-runtime-versions@1.0.0", "", {}, "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ=="],
|
||||
|
||||
"@expo/spawn-async": ["@expo/spawn-async@1.7.2", "", { "dependencies": { "cross-spawn": "^7.0.3" } }, "sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew=="],
|
||||
|
||||
"@expo/sudo-prompt": ["@expo/sudo-prompt@9.3.2", "", {}, "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw=="],
|
||||
|
||||
"@expo/vector-icons": ["@expo/vector-icons@15.1.1", "", { "peerDependencies": { "expo-font": ">=14.0.4", "react": "*", "react-native": "*" } }, "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw=="],
|
||||
|
||||
"@expo/ws-tunnel": ["@expo/ws-tunnel@1.0.6", "", {}, "sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q=="],
|
||||
|
||||
"@expo/xcpretty": ["@expo/xcpretty@4.4.3", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "chalk": "^4.1.0", "js-yaml": "^4.1.0" }, "bin": { "excpretty": "build/cli.js" } }, "sha512-wC562eD3gS6vO2tWHToFhlFnmHKfKHgF1oyvojeSkLK/ZYop1bMU+7cOMiF9Sq70CzcsLy/EMRy/uRc76QmNRw=="],
|
||||
|
||||
"@expressive-code/core": ["@expressive-code/core@0.41.7", "", { "dependencies": { "@ctrl/tinycolor": "^4.0.4", "hast-util-select": "^6.0.2", "hast-util-to-html": "^9.0.1", "hast-util-to-text": "^4.0.1", "hastscript": "^9.0.0", "postcss": "^8.4.38", "postcss-nested": "^6.0.1", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1" } }, "sha512-ck92uZYZ9Wba2zxkiZLsZGi9N54pMSAVdrI9uW3Oo9AtLglD5RmrdTwbYPCT2S/jC36JGB2i+pnQtBm/Ib2+dg=="],
|
||||
|
||||
"@expressive-code/plugin-frames": ["@expressive-code/plugin-frames@0.41.7", "", { "dependencies": { "@expressive-code/core": "^0.41.7" } }, "sha512-diKtxjQw/979cTglRFaMCY/sR6hWF0kSMg8jsKLXaZBSfGS0I/Hoe7Qds3vVEgeoW+GHHQzMcwvgx/MOIXhrTA=="],
|
||||
@@ -1194,6 +1464,8 @@
|
||||
|
||||
"@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="],
|
||||
|
||||
"@fugood/react-native-audio-pcm-stream": ["@fugood/react-native-audio-pcm-stream@1.1.4", "", {}, "sha512-M6H6ay4ea0vpioII9T/C9qXFPeGpxGN24nl0REP2/wtsorZXg3zzHjZbf3UUUwjf6lEEHMlGCJfXUsxwC/vV8w=="],
|
||||
|
||||
"@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="],
|
||||
|
||||
"@gitlab/opencode-gitlab-auth": ["@gitlab/opencode-gitlab-auth@1.3.3", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-FT+KsCmAJjtqWr1YAq0MywGgL9kaLQ4apmsoowAXrPqHtoYf2i/nY10/A+L06kNj22EATeEDRpbB1NWXMto/SA=="],
|
||||
@@ -1218,6 +1490,16 @@
|
||||
|
||||
"@hono/zod-validator": ["@hono/zod-validator@0.4.2", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-1rrlBg+EpDPhzOV4hT9pxr5+xDVmKuz6YJl+la7VCwK6ass5ldyKm5fD+umJdV2zhHD6jROoCCv8NbTwyfhT0g=="],
|
||||
|
||||
"@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="],
|
||||
|
||||
"@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="],
|
||||
|
||||
"@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="],
|
||||
|
||||
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
|
||||
|
||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
||||
|
||||
"@ibm/plex": ["@ibm/plex@6.4.1", "", { "dependencies": { "@ibm/telemetry-js": "^1.5.1" } }, "sha512-fnsipQywHt3zWvsnlyYKMikcVI7E2fEwpiPnIHFqlbByXVfQfANAAeJk1IV4mNnxhppUIDlhU0TzwYwL++Rn2g=="],
|
||||
|
||||
"@ibm/telemetry-js": ["@ibm/telemetry-js@1.11.0", "", { "bin": { "ibmtelemetry": "dist/collect.js" } }, "sha512-RO/9j+URJnSfseWg9ZkEX9p+a3Ousd33DBU7rOafoZB08RqdzxFVYJ2/iM50dkBuD0o7WX7GYt1sLbNgCoE+pA=="],
|
||||
@@ -1276,6 +1558,24 @@
|
||||
|
||||
"@isaacs/string-locale-compare": ["@isaacs/string-locale-compare@1.1.0", "", {}, "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="],
|
||||
|
||||
"@isaacs/ttlcache": ["@isaacs/ttlcache@1.4.1", "", {}, "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="],
|
||||
|
||||
"@istanbuljs/schema": ["@istanbuljs/schema@0.1.6", "", {}, "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw=="],
|
||||
|
||||
"@jest/create-cache-key-function": ["@jest/create-cache-key-function@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3" } }, "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA=="],
|
||||
|
||||
"@jest/environment": ["@jest/environment@29.7.0", "", { "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0" } }, "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw=="],
|
||||
|
||||
"@jest/fake-timers": ["@jest/fake-timers@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ=="],
|
||||
|
||||
"@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
|
||||
|
||||
"@jest/transform": ["@jest/transform@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", "write-file-atomic": "^4.0.2" } }, "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw=="],
|
||||
|
||||
"@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="],
|
||||
|
||||
"@jimp/core": ["@jimp/core@1.6.0", "", { "dependencies": { "@jimp/file-ops": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^16.0.0", "mime": "3" } }, "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w=="],
|
||||
|
||||
"@jimp/diff": ["@jimp/diff@1.6.0", "", { "dependencies": { "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "pixelmatch": "^5.3.0" } }, "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw=="],
|
||||
@@ -1536,6 +1836,8 @@
|
||||
|
||||
"@openauthjs/openauth": ["@openauthjs/openauth@0.0.0-20250322224806", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-p5IWSRXvABcwocH2dNI0w8c1QJelIOFulwhKk+aLLFfUbs8u1pr7kQbYe8yCSM2+bcLHiwbogpUQc2ovrGwCuw=="],
|
||||
|
||||
"@opencode-ai/apn-relay": ["@opencode-ai/apn-relay@workspace:packages/apn-relay"],
|
||||
|
||||
"@opencode-ai/app": ["@opencode-ai/app@workspace:packages/app"],
|
||||
|
||||
"@opencode-ai/console-app": ["@opencode-ai/console-app@workspace:packages/console/app"],
|
||||
@@ -1840,6 +2142,8 @@
|
||||
|
||||
"@radix-ui/react-context": ["@radix-ui/react-context@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" }, "optionalPeers": ["@types/react"] }, "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg=="],
|
||||
|
||||
"@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="],
|
||||
|
||||
"@radix-ui/react-direction": ["@radix-ui/react-direction@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" }, "optionalPeers": ["@types/react"] }, "sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA=="],
|
||||
|
||||
"@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.0.4", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/primitive": "1.0.1", "@radix-ui/react-compose-refs": "1.0.1", "@radix-ui/react-primitive": "1.0.3", "@radix-ui/react-use-callback-ref": "1.0.1", "@radix-ui/react-use-escape-keydown": "1.0.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7UpBa/RKMoHJYjie1gkF1DlK8l1fdU/VKDpoS3rCCo8YBJR294GwcEHyxHw72yvphJ7ld0AXEcSLAzY2F/WyCg=="],
|
||||
@@ -1864,6 +2168,8 @@
|
||||
|
||||
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.0.2", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-compose-refs": "1.0.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" }, "optionalPeers": ["@types/react"] }, "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg=="],
|
||||
|
||||
"@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="],
|
||||
|
||||
"@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.0.3", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/primitive": "1.0.1", "@radix-ui/react-primitive": "1.0.3", "@radix-ui/react-use-controllable-state": "1.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg=="],
|
||||
|
||||
"@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.0.4", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/primitive": "1.0.1", "@radix-ui/react-context": "1.0.1", "@radix-ui/react-direction": "1.0.1", "@radix-ui/react-primitive": "1.0.3", "@radix-ui/react-roving-focus": "1.0.4", "@radix-ui/react-toggle": "1.0.3", "@radix-ui/react-use-controllable-state": "1.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A=="],
|
||||
@@ -1874,6 +2180,8 @@
|
||||
|
||||
"@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-use-callback-ref": "1.0.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" }, "optionalPeers": ["@types/react"] }, "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA=="],
|
||||
|
||||
"@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="],
|
||||
|
||||
"@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.0.3", "", { "dependencies": { "@babel/runtime": "^7.13.10", "@radix-ui/react-use-callback-ref": "1.0.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" }, "optionalPeers": ["@types/react"] }, "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg=="],
|
||||
|
||||
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" }, "optionalPeers": ["@types/react"] }, "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ=="],
|
||||
@@ -1886,6 +2194,42 @@
|
||||
|
||||
"@radix-ui/rect": ["@radix-ui/rect@1.0.1", "", { "dependencies": { "@babel/runtime": "^7.13.10" } }, "sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ=="],
|
||||
|
||||
"@react-native/assets-registry": ["@react-native/assets-registry@0.83.4", "", {}, "sha512-aqKtpbJDSQeSX/Dwv0yMe1/Rd2QfXi12lnyZDXNn/OEKz59u6+LuPBVgO/9CRyclHmdlvwg8c7PJ9eX2ZMnjWg=="],
|
||||
|
||||
"@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.83.4", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.83.4" } }, "sha512-UFsK+c1rvT84XZfzpmwKePsc5nTr5LK7hh18TI0DooNlVcztDbMDsQZpDnhO/gmk7aTbWEqO5AB3HJ7tvGp+Jg=="],
|
||||
|
||||
"@react-native/babel-preset": ["@react-native/babel-preset@0.83.4", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.83.4", "babel-plugin-syntax-hermes-parser": "0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" } }, "sha512-SXPFn3Jp4gOzlBDnDOKPzMfxQPKJMYJs05EmEeFB/6km46xZ9l+2YKXwAwxfNhHnmwNf98U/bnVndU95I0TMCw=="],
|
||||
|
||||
"@react-native/codegen": ["@react-native/codegen@0.83.4", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.32.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" } }, "sha512-CJ7XutzIqJPz3Lp/5TOiRWlU/JAjTboMT1BHNLSXjYHXwTmgHM3iGEbpCOtBMjWvsojRTJyRO/G3ghInIIXEYg=="],
|
||||
|
||||
"@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.83.4", "", { "dependencies": { "@react-native/dev-middleware": "0.83.4", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.83.3", "metro-config": "^0.83.3", "metro-core": "^0.83.3", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*", "@react-native/metro-config": "*" }, "optionalPeers": ["@react-native-community/cli", "@react-native/metro-config"] }, "sha512-8os0weQEnjUhWy7Db881+JKRwNHVGM40VtTRvltAyA/YYkrGg4kPCqiTybMxQDEcF3rnviuxHyI+ITiglfmgmQ=="],
|
||||
|
||||
"@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.83.4", "", {}, "sha512-mCE2s/S7SEjax3gZb6LFAraAI3x13gRVWJWqT0HIm71e4ITObENNTDuMw4mvZ/wr4Gz2wv4FcBH5/Nla9LXOcg=="],
|
||||
|
||||
"@react-native/debugger-shell": ["@react-native/debugger-shell@0.83.4", "", { "dependencies": { "cross-spawn": "^7.0.6", "fb-dotslash": "0.5.8" } }, "sha512-FtAnrvXqy1xeZ+onwilvxEeeBsvBlhtfrHVIC2R/BOJAK9TbKEtFfjio0wsn3DQIm+UZq48DSa+p9jJZ2aJUww=="],
|
||||
|
||||
"@react-native/dev-middleware": ["@react-native/dev-middleware@0.83.4", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.83.4", "@react-native/debugger-shell": "0.83.4", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^7.5.10" } }, "sha512-3s9nXZc/kj986nI2RPqxiIJeTS3o7pvZDxbHu7GE9WVIGX9YucA1l/tEiXd7BAm3TBFOfefDOT08xD46wH+R3Q=="],
|
||||
|
||||
"@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.83.4", "", {}, "sha512-AhaSWw2k3eMKqZ21IUdM7rpyTYOpAfsBbIIiom1QQii3QccX0uW2AWTcRhfuWRxqr2faGFaOBYedWl2fzp5hgw=="],
|
||||
|
||||
"@react-native/js-polyfills": ["@react-native/js-polyfills@0.83.4", "", {}, "sha512-wYUdv0rt4MjhKhQloO1AnGDXhZQOFZHDxm86dEtEA0WcsCdVrFdRULFM+rKUC/QQtJW2rS6WBqtBusgtrsDADg=="],
|
||||
|
||||
"@react-native/normalize-colors": ["@react-native/normalize-colors@0.83.4", "", {}, "sha512-9ezxaHjxqTkTOLg62SGg7YhFaE+fxa/jlrWP0nwf7eGFHlGOiTAaRR2KUfiN3K05e+EMbEhgcH/c7bgaXeGyJw=="],
|
||||
|
||||
"@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.83.4", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.2.0", "react": "*", "react-native": "*" }, "optionalPeers": ["@types/react"] }, "sha512-vNF/8kokMW8JEjG4n+j7veLTjHRRABlt4CaTS6+wtqzvWxCJHNIC8fhCqrDPn9fIn8sNePd8DyiFVX5L9TBBRA=="],
|
||||
|
||||
"@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.15.9", "", { "dependencies": { "@react-navigation/elements": "^2.9.14", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.2.2", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-Ou28A1aZLj5wiFQ3F93aIsrI4NCwn3IJzkkjNo9KLFXsc0Yks+UqrVaFlffHFLsrbajuGRG/OQpnMA1ljayY5Q=="],
|
||||
|
||||
"@react-navigation/core": ["@react-navigation/core@7.17.2", "", { "dependencies": { "@react-navigation/routers": "^7.5.3", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-Rt2OZwcgOmjv401uLGAKaRM6xo0fiBce/A7LfRHI1oe5FV+KooWcgAoZ2XOtgKj6UzVMuQWt3b2e6rxo/mDJRA=="],
|
||||
|
||||
"@react-navigation/elements": ["@react-navigation/elements@2.9.14", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.2.2", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-lKqzu+su2pI/YIZmR7L7xdOs4UL+rVXKJAMpRMBrwInEy96SjIFst6QDGpE89Dunnu3VjVpjWfByo9f2GWBHDQ=="],
|
||||
|
||||
"@react-navigation/native": ["@react-navigation/native@7.2.2", "", { "dependencies": { "@react-navigation/core": "^7.17.2", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-kem1Ko2BcbAjmbQIv66dNmr6EtfDut3QU0qjsVhMnLLhktwyXb6FzZYp8gTrUb6AvkAbaJoi+BF5Pl55pAUa5w=="],
|
||||
|
||||
"@react-navigation/native-stack": ["@react-navigation/native-stack@7.14.11", "", { "dependencies": { "@react-navigation/elements": "^2.9.14", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.2.2", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-1ufBtJ7KbVFlQhXsYSYHqjgkmP30AzJSgW48YjWMQZ3NZGAyYe34w9Wd4KpdebQCfDClPe9maU+8crA/awa6lQ=="],
|
||||
|
||||
"@react-navigation/routers": ["@react-navigation/routers@7.5.3", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg=="],
|
||||
|
||||
"@remix-run/node-fetch-server": ["@remix-run/node-fetch-server@0.8.1", "", {}, "sha512-J1dev372wtJqmqn9U/qbpbZxbJSQrogNN2+Qv1lKlpATpe/WQ9aCZfl/xSb9d2Rgh1IyLSvNxZAXPZxruO6Xig=="],
|
||||
|
||||
"@remix-run/router": ["@remix-run/router@1.9.0", "", {}, "sha512-bV63itrKBC0zdT27qYm6SDZHlkXwFL1xMBuhkn+X7l0+IIhNaH5wuuvZKp6eKhCD4KFhujhfhCT1YxXW6esUIA=="],
|
||||
@@ -1974,8 +2318,14 @@
|
||||
|
||||
"@sigstore/verify": ["@sigstore/verify@3.1.0", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag=="],
|
||||
|
||||
"@sinclair/typebox": ["@sinclair/typebox@0.27.10", "", {}, "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA=="],
|
||||
|
||||
"@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="],
|
||||
|
||||
"@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="],
|
||||
|
||||
"@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="],
|
||||
|
||||
"@slack/bolt": ["@slack/bolt@3.22.0", "", { "dependencies": { "@slack/logger": "^4.0.0", "@slack/oauth": "^2.6.3", "@slack/socket-mode": "^1.3.6", "@slack/types": "^2.13.0", "@slack/web-api": "^6.13.0", "@types/express": "^4.16.1", "@types/promise.allsettled": "^1.0.3", "@types/tsscmp": "^1.0.0", "axios": "^1.7.4", "express": "^4.21.0", "path-to-regexp": "^8.1.0", "promise.allsettled": "^1.0.2", "raw-body": "^2.3.3", "tsscmp": "^1.0.6" } }, "sha512-iKDqGPEJDnrVwxSVlFW6OKTkijd7s4qLBeSufoBsTM0reTyfdp/5izIQVkxNfzjHi3o6qjdYbRXkYad5HBsBog=="],
|
||||
|
||||
"@slack/logger": ["@slack/logger@4.0.1", "", { "dependencies": { "@types/node": ">=18" } }, "sha512-6cmdPrV/RYfd2U0mDGiMK8S7OJqpCTm7enMLRR3edccsPX8j7zXTLnaEF4fhxxJJTAIOil6+qZrnUPTuaLvwrQ=="],
|
||||
@@ -2314,6 +2664,10 @@
|
||||
|
||||
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
||||
|
||||
"@types/emscripten": ["@types/emscripten@1.41.5", "", {}, "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q=="],
|
||||
|
||||
"@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
|
||||
@@ -2326,6 +2680,10 @@
|
||||
|
||||
"@types/fs-extra": ["@types/fs-extra@9.0.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA=="],
|
||||
|
||||
"@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="],
|
||||
|
||||
"@types/hammerjs": ["@types/hammerjs@2.0.46", "", {}, "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw=="],
|
||||
|
||||
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
|
||||
|
||||
"@types/http-cache-semantics": ["@types/http-cache-semantics@4.2.0", "", {}, "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q=="],
|
||||
@@ -2334,6 +2692,12 @@
|
||||
|
||||
"@types/is-stream": ["@types/is-stream@1.1.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg=="],
|
||||
|
||||
"@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="],
|
||||
|
||||
"@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="],
|
||||
|
||||
"@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="],
|
||||
|
||||
"@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
@@ -2384,6 +2748,8 @@
|
||||
|
||||
"@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
|
||||
|
||||
"@types/qrcode": ["@types/qrcode@1.5.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg=="],
|
||||
|
||||
"@types/qs": ["@types/qs@6.15.0", "", {}, "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow=="],
|
||||
|
||||
"@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="],
|
||||
@@ -2408,6 +2774,8 @@
|
||||
|
||||
"@types/ssri": ["@types/ssri@7.1.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-odD/56S3B51liILSk5aXJlnYt99S6Rt9EFDDqGtJM26rKHApHcwyU/UoYHrzKkdkHMAIquGWCuHtQTbes+FRQw=="],
|
||||
|
||||
"@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="],
|
||||
|
||||
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
|
||||
|
||||
"@types/tsscmp": ["@types/tsscmp@1.0.2", "", {}, "sha512-cy7BRSU8GYYgxjcx0Py+8lo5MthuDhlyu076KUcYzVNXL23luYgRHkMG2fIFEc6neckeh/ntP82mw+U4QjZq+g=="],
|
||||
@@ -2432,6 +2800,26 @@
|
||||
|
||||
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.2", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.2", "@typescript-eslint/type-utils": "8.58.2", "@typescript-eslint/utils": "8.58.2", "@typescript-eslint/visitor-keys": "8.58.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.2", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.2", "@typescript-eslint/types": "8.58.2", "@typescript-eslint/typescript-estree": "8.58.2", "@typescript-eslint/visitor-keys": "8.58.2", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg=="],
|
||||
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.2", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.2", "@typescript-eslint/types": "^8.58.2", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.2", "", { "dependencies": { "@typescript-eslint/types": "8.58.2", "@typescript-eslint/visitor-keys": "8.58.2" } }, "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q=="],
|
||||
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.2", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.58.2", "", { "dependencies": { "@typescript-eslint/types": "8.58.2", "@typescript-eslint/typescript-estree": "8.58.2", "@typescript-eslint/utils": "8.58.2", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.58.2", "", {}, "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.2", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.2", "@typescript-eslint/tsconfig-utils": "8.58.2", "@typescript-eslint/types": "8.58.2", "@typescript-eslint/visitor-keys": "8.58.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.2", "@typescript-eslint/types": "8.58.2", "@typescript-eslint/typescript-estree": "8.58.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.2", "", { "dependencies": { "@typescript-eslint/types": "8.58.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA=="],
|
||||
|
||||
"@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20251207.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251207.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251207.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20251207.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251207.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20251207.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251207.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20251207.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-4QcRnzB0pi9rS0AOvg8kWbmuwHv5X7B2EXHbgcms9+56hsZ8SZrZjNgBJb2rUIodJ4kU5mrkj/xlTTT4r9VcpQ=="],
|
||||
|
||||
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20251207.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-waWJnuuvkXh4WdpbTjYf7pyahJzx0ycesV2BylyHrE9OxU9FSKcD/cRLQYvbq3YcBSdF7sZwRLDBer7qTeLsYA=="],
|
||||
@@ -2528,10 +2916,14 @@
|
||||
|
||||
"ajv-keywords": ["ajv-keywords@3.5.2", "", { "peerDependencies": { "ajv": "^6.9.1" } }, "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="],
|
||||
|
||||
"anser": ["anser@1.4.10", "", {}, "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww=="],
|
||||
|
||||
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
|
||||
|
||||
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
|
||||
|
||||
"ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
@@ -2554,7 +2946,7 @@
|
||||
|
||||
"arctic": ["arctic@2.3.4", "", { "dependencies": { "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", "@oslojs/jwt": "0.2.0" } }, "sha512-+p30BOWsctZp+CVYCt7oAean/hWGW42sH5LAcRQX56ttEkFJWbzXBhmSpibbzwSJkRrotmsA+oAoJoVsU0f5xA=="],
|
||||
|
||||
"arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
|
||||
"arg": ["arg@4.1.3", "", {}, "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
@@ -2574,6 +2966,8 @@
|
||||
|
||||
"arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
|
||||
|
||||
"asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="],
|
||||
|
||||
"assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="],
|
||||
|
||||
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||
@@ -2626,16 +3020,46 @@
|
||||
|
||||
"babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.12", "", { "dependencies": { "@babel/core": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig=="],
|
||||
|
||||
"babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="],
|
||||
|
||||
"babel-plugin-istanbul": ["babel-plugin-istanbul@6.1.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" } }, "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA=="],
|
||||
|
||||
"babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@29.6.3", "", { "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg=="],
|
||||
|
||||
"babel-plugin-jsx-dom-expressions": ["babel-plugin-jsx-dom-expressions@0.40.6", "", { "dependencies": { "@babel/helper-module-imports": "7.18.6", "@babel/plugin-syntax-jsx": "^7.18.6", "@babel/types": "^7.20.7", "html-entities": "2.3.3", "parse5": "^7.1.2" }, "peerDependencies": { "@babel/core": "^7.20.12" } }, "sha512-v3P1MW46Lm7VMpAkq0QfyzLWWkC8fh+0aE5Km4msIgDx5kjenHU0pF2s+4/NH8CQn/kla6+Hvws+2AF7bfV5qQ=="],
|
||||
|
||||
"babel-plugin-module-resolver": ["babel-plugin-module-resolver@5.0.2", "", { "dependencies": { "find-babel-config": "^2.1.1", "glob": "^9.3.3", "pkg-up": "^3.1.0", "reselect": "^4.1.7", "resolve": "^1.22.8" } }, "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg=="],
|
||||
|
||||
"babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.17", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w=="],
|
||||
|
||||
"babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.13.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A=="],
|
||||
|
||||
"babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.8", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg=="],
|
||||
|
||||
"babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
|
||||
|
||||
"babel-plugin-react-native-web": ["babel-plugin-react-native-web@0.21.2", "", {}, "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA=="],
|
||||
|
||||
"babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.32.1", "", { "dependencies": { "hermes-parser": "0.32.1" } }, "sha512-HgErPZTghW76Rkq9uqn5ESeiD97FbqpZ1V170T1RG2RDp+7pJVQV2pQJs7y5YzN0/gcT6GM5ci9apRnIwuyPdQ=="],
|
||||
|
||||
"babel-plugin-transform-flow-enums": ["babel-plugin-transform-flow-enums@0.0.2", "", { "dependencies": { "@babel/plugin-syntax-flow": "^7.12.1" } }, "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ=="],
|
||||
|
||||
"babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.2.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg=="],
|
||||
|
||||
"babel-preset-expo": ["babel-preset-expo@55.0.17", "", { "dependencies": { "@babel/generator": "^7.20.5", "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-preset": "0.83.4", "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4", "resolve-from": "^5.0.0" }, "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", "expo-widgets": "^55.0.13", "react-refresh": ">=0.14.0 <1.0.0" }, "optionalPeers": ["@babel/runtime", "expo", "expo-widgets"] }, "sha512-voPAKycqeqOE+4g/nW6gGaNPMnj3MYCYbVEZlZDUlztGVxlKKkUD+xwlK0ZU/uy6HxAY+tjBEpvsabD5g6b2oQ=="],
|
||||
|
||||
"babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="],
|
||||
|
||||
"babel-preset-solid": ["babel-preset-solid@1.9.12", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.6" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.12" }, "optionalPeers": ["solid-js"] }, "sha512-LLqnuKVDlKpyBlMPcH6qEvs/wmS9a+NczppxJ3ryS/c0O5IiSFOIBQi9GzyiGDSbcJpx4Gr87jyFTos1MyEuWg=="],
|
||||
|
||||
"badgin": ["badgin@1.2.3", "", {}, "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw=="],
|
||||
|
||||
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
|
||||
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
||||
"barcode-detector": ["barcode-detector@3.1.2", "", { "dependencies": { "zxing-wasm": "3.0.2" } }, "sha512-Q5kjXpVH5I3ItykNzbWmfWnNryFN1ZTWp10k9/PKJuS0RnoKR7jTrHEJODR4fn04bRomq7TJwie/Dr9fj/GoGQ=="],
|
||||
|
||||
"bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="],
|
||||
|
||||
"bare-fs": ["bare-fs@4.7.0", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-xzqKsCFxAek9aezYhjJuJRXBIaYlg/0OGDTZp+T8eYmYMlm66cs6cYko02drIyjN2CBbi+I6L7YfXyqpqtKRXA=="],
|
||||
@@ -2660,6 +3084,10 @@
|
||||
|
||||
"before-after-hook": ["before-after-hook@2.2.3", "", {}, "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="],
|
||||
|
||||
"better-opn": ["better-opn@3.0.2", "", { "dependencies": { "open": "^8.0.4" } }, "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ=="],
|
||||
|
||||
"big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="],
|
||||
|
||||
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
|
||||
|
||||
"bin-links": ["bin-links@6.0.0", "", { "dependencies": { "cmd-shim": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "proc-log": "^6.0.0", "read-cmd-shim": "^6.0.0", "write-file-atomic": "^7.0.0" } }, "sha512-X4CiKlcV2GjnCMwnKAfbVWpHa++65th9TuzAEYtZoATiOE2DQKhSp4CJlyLoTqdhBKlXjpXjCTYPNNFS33Fi6w=="],
|
||||
@@ -2690,6 +3118,10 @@
|
||||
|
||||
"boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="],
|
||||
|
||||
"bplist-creator": ["bplist-creator@0.1.0", "", { "dependencies": { "stream-buffers": "2.2.x" } }, "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg=="],
|
||||
|
||||
"bplist-parser": ["bplist-parser@0.3.2", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
@@ -2698,6 +3130,8 @@
|
||||
|
||||
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
|
||||
|
||||
"bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="],
|
||||
|
||||
"buffer": ["buffer@4.9.2", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", "isarray": "^1.0.0" } }, "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg=="],
|
||||
|
||||
"buffer-crc32": ["buffer-crc32@1.0.0", "", {}, "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w=="],
|
||||
@@ -2784,6 +3218,10 @@
|
||||
|
||||
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
|
||||
|
||||
"chrome-launcher": ["chrome-launcher@0.15.2", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0" }, "bin": { "print-chrome-path": "bin/print-chrome-path.js" } }, "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ=="],
|
||||
|
||||
"chromium-edge-launcher": ["chromium-edge-launcher@0.2.0", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0", "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg=="],
|
||||
|
||||
"chromium-pickle-js": ["chromium-pickle-js@0.2.0", "", {}, "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw=="],
|
||||
|
||||
"ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="],
|
||||
@@ -2796,7 +3234,7 @@
|
||||
|
||||
"cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
|
||||
|
||||
"cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
|
||||
"cli-cursor": ["cli-cursor@2.1.0", "", { "dependencies": { "restore-cursor": "^2.0.0" } }, "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw=="],
|
||||
|
||||
"cli-sound": ["cli-sound@1.1.3", "", { "dependencies": { "find-exec": "^1.0.3" }, "bin": { "cli-sound": "dist/esm/cli.js" } }, "sha512-dpdF3KS3wjo1fobKG5iU9KyKqzQWAqueymHzZ9epus/dZ40487gAvS6aXFeBul+GiQAQYUTAtUWgQvw6Jftbyg=="],
|
||||
|
||||
@@ -2804,6 +3242,8 @@
|
||||
|
||||
"cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="],
|
||||
|
||||
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
||||
|
||||
"clipboardy": ["clipboardy@4.0.0", "", { "dependencies": { "execa": "^8.0.1", "is-wsl": "^3.1.0", "is64bit": "^2.0.0" } }, "sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w=="],
|
||||
|
||||
"cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
|
||||
@@ -2844,6 +3284,10 @@
|
||||
|
||||
"compress-commons": ["compress-commons@6.0.2", "", { "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", "is-stream": "^2.0.1", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg=="],
|
||||
|
||||
"compressible": ["compressible@2.0.18", "", { "dependencies": { "mime-db": ">= 1.43.0 < 2" } }, "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg=="],
|
||||
|
||||
"compression": ["compression@1.8.1", "", { "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" } }, "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"condense-newlines": ["condense-newlines@0.2.1", "", { "dependencies": { "extend-shallow": "^2.0.1", "is-whitespace": "^0.3.0", "kind-of": "^3.0.2" } }, "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg=="],
|
||||
@@ -2854,6 +3298,8 @@
|
||||
|
||||
"config-chain": ["config-chain@1.1.13", "", { "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" } }, "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="],
|
||||
|
||||
"connect": ["connect@3.7.0", "", { "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" } }, "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ=="],
|
||||
|
||||
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
|
||||
|
||||
"content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
|
||||
@@ -2868,6 +3314,8 @@
|
||||
|
||||
"cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="],
|
||||
|
||||
"core-js-compat": ["core-js-compat@3.49.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA=="],
|
||||
|
||||
"core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="],
|
||||
|
||||
"cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
|
||||
@@ -2886,6 +3334,8 @@
|
||||
|
||||
"crossws": ["crossws@0.4.5", "", { "peerDependencies": { "srvx": ">=0.11.5" }, "optionalPeers": ["srvx"] }, "sha512-wUR89x/Rw7/8t+vn0CmGDYM9TD6VtARGb0LD5jq2wjtMy1vCP4M+sm6N6TigWeTYvnA8MoW29NqqXD0ep0rfBA=="],
|
||||
|
||||
"css-in-js-utils": ["css-in-js-utils@3.1.0", "", { "dependencies": { "hyphenate-style-name": "^1.0.3" } }, "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A=="],
|
||||
|
||||
"css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
|
||||
|
||||
"css-selector-parser": ["css-selector-parser@3.3.0", "", {}, "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g=="],
|
||||
@@ -2914,14 +3364,20 @@
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
|
||||
|
||||
"decimal.js": ["decimal.js@10.5.0", "", {}, "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="],
|
||||
|
||||
"decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
|
||||
|
||||
"decode-uri-component": ["decode-uri-component@0.2.2", "", {}, "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="],
|
||||
|
||||
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
|
||||
|
||||
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||
|
||||
"default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
|
||||
@@ -2972,6 +3428,8 @@
|
||||
|
||||
"diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="],
|
||||
|
||||
"dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="],
|
||||
|
||||
"dir-compare": ["dir-compare@4.2.0", "", { "dependencies": { "minimatch": "^3.0.5", "p-limit": "^3.1.0 " } }, "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ=="],
|
||||
|
||||
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
|
||||
@@ -2986,6 +3444,8 @@
|
||||
|
||||
"dns-packet": ["dns-packet@5.6.1", "", { "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" } }, "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw=="],
|
||||
|
||||
"dnssd-advertise": ["dnssd-advertise@1.1.4", "", {}, "sha512-AmGyK9WpNf06WeP5TjHZq/wNzP76OuEeaiTlKr9E/EEelYLczywUKoqRz+DPRq/ErssjT4lU+/W7wzJW+7K/ZA=="],
|
||||
|
||||
"dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
|
||||
|
||||
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
|
||||
@@ -3116,10 +3576,26 @@
|
||||
|
||||
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@10.2.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.5.5", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q=="],
|
||||
|
||||
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.1.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="],
|
||||
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
||||
|
||||
"espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="],
|
||||
|
||||
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
|
||||
|
||||
"esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
|
||||
|
||||
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"estree-util-attach-comments": ["estree-util-attach-comments@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="],
|
||||
|
||||
"estree-util-build-jsx": ["estree-util-build-jsx@3.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-walker": "^3.0.0" } }, "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ=="],
|
||||
@@ -3134,6 +3610,8 @@
|
||||
|
||||
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
|
||||
|
||||
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
|
||||
@@ -3156,6 +3634,76 @@
|
||||
|
||||
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
|
||||
|
||||
"expo": ["expo@55.0.15", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "55.0.24", "@expo/config": "~55.0.15", "@expo/config-plugins": "~55.0.8", "@expo/devtools": "55.0.2", "@expo/fingerprint": "0.16.6", "@expo/local-build-cache-provider": "55.0.11", "@expo/log-box": "55.0.10", "@expo/metro": "~55.0.0", "@expo/metro-config": "55.0.16", "@expo/vector-icons": "^15.0.2", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "~55.0.17", "expo-asset": "~55.0.15", "expo-constants": "~55.0.14", "expo-file-system": "~55.0.16", "expo-font": "~55.0.6", "expo-keep-awake": "~55.0.6", "expo-modules-autolinking": "55.0.17", "expo-modules-core": "55.0.22", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-minimum": "^0.1.1" }, "peerDependencies": { "@expo/dom-webview": "*", "@expo/metro-runtime": "*", "react": "*", "react-native": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/dom-webview", "@expo/metro-runtime", "react-native-webview"], "bin": { "expo": "bin/cli", "fingerprint": "bin/fingerprint", "expo-modules-autolinking": "bin/autolinking" } }, "sha512-sHIvqG477UU1jZHhaexXbUgsU7y+xnYZqDW1HrUkEBYiuEb5lobvWLmwea76EBVkityQx46UDtepFtarpUJQqQ=="],
|
||||
|
||||
"expo-application": ["expo-application@55.0.14", "", { "peerDependencies": { "expo": "*" } }, "sha512-NgqDIt3eCf4aVLp1L6AcEanCYoyJeuBsGrgGSzOIvxAsOvp5X3SYKW3ROgpKUnLQEKMWlzwETpjsUGszcqkk8g=="],
|
||||
|
||||
"expo-asset": ["expo-asset@55.0.15", "", { "dependencies": { "@expo/image-utils": "^0.8.13", "expo-constants": "~55.0.14" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-d3FIpHJ6ZngYXxRItYWBGT5H8Wkk7/l4fMe8Mmd2xDyKrO0/CM7c8r/J5M71D+BJr5P3My8wertGYZXHSiZYxQ=="],
|
||||
|
||||
"expo-audio": ["expo-audio@55.0.13", "", { "peerDependencies": { "expo": "*", "expo-asset": "*", "react": "*", "react-native": "*" } }, "sha512-rY9C81mSE6HHCPtyeCv53nRrBL1Su7JpQVuvbMFOA7AOY7xppg3Gq1SFybiDIiNQunDfcUUc2b8eDO8vkO0Iag=="],
|
||||
|
||||
"expo-camera": ["expo-camera@55.0.15", "", { "dependencies": { "barcode-detector": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-WRVsZf+2p7EsxudwyiUMYijJS8M98t/BVP6yG7N+08JSUotkGjmZcemom1gM36uy27P8QsSVP0hD+FravmQiBA=="],
|
||||
|
||||
"expo-constants": ["expo-constants@55.0.14", "", { "dependencies": { "@expo/config": "~55.0.15", "@expo/env": "~2.1.1" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-l23QVQCYBPKT5zbxxZdJeuhiunadvWdjcQ9+GC8h+02jCoLmWRk20064nCINnQTP3Hf+uLPteUiwYrJd0e446w=="],
|
||||
|
||||
"expo-dev-client": ["expo-dev-client@55.0.27", "", { "dependencies": { "expo-dev-launcher": "55.0.28", "expo-dev-menu": "55.0.23", "expo-dev-menu-interface": "55.0.2", "expo-manifests": "~55.0.15", "expo-updates-interface": "~55.1.5" }, "peerDependencies": { "expo": "*" } }, "sha512-Ala2ER6v1NPFySdnuQN/GpQZI9r7E9eSMvzD32vv8A4FJkPfzjvj6U5uH4IytQQLVTa837fknXRvOMwRb9ZYQQ=="],
|
||||
|
||||
"expo-dev-launcher": ["expo-dev-launcher@55.0.28", "", { "dependencies": { "@expo/schema-utils": "^55.0.3", "expo-dev-menu": "55.0.23", "expo-manifests": "~55.0.15" }, "peerDependencies": { "expo": "*" } }, "sha512-jwCD7YeGqZMWrS4KODb51kPBDta8bDrPjhYCnodglcQ0jhPVsAnVSbtsQ2/TaaYUUtv+ipm1ufhNTFtcwya9kA=="],
|
||||
|
||||
"expo-dev-menu": ["expo-dev-menu@55.0.23", "", { "dependencies": { "expo-dev-menu-interface": "55.0.2" }, "peerDependencies": { "expo": "*" } }, "sha512-m6B2EwkoX9hwzP50EZPX8vc/szGfvNFsUTw3ExTWnxiJ9/IM0WfCFYjt8siFjMHcyblIjBt/XLN6mw6q8m2AEg=="],
|
||||
|
||||
"expo-dev-menu-interface": ["expo-dev-menu-interface@55.0.2", "", { "peerDependencies": { "expo": "*" } }, "sha512-DomUNvGzY/xliwnMdbAYY780sCv19N7zIbifc0ClcoCzJZpNSCkvJ2qGIFRPyM/7DmqmlHGCKi8di7kYYLKNEg=="],
|
||||
|
||||
"expo-device": ["expo-device@55.0.15", "", { "dependencies": { "ua-parser-js": "^0.7.33" }, "peerDependencies": { "expo": "*" } }, "sha512-vXy4U/IeYI+zHGG45Ap6J7EuyQmkstyo8I+/5YGr5q2zmqLBo6SWE62wii8i9hLHheHn6AtF9UPrSWAREJrE8A=="],
|
||||
|
||||
"expo-eas-client": ["expo-eas-client@55.0.5", "", {}, "sha512-wRagCeSbSnSGVXgP7V+qiGfXzZ9hTVKWvKIOP7lwrX3MIEenNmNlO4D3RVC3aNU2GhmO3ZCZIIEre80KZoUUHA=="],
|
||||
|
||||
"expo-file-system": ["expo-file-system@55.0.16", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-EetQ/zVFK07Vmz4Yke0fvoES4xVwScTdd0PMoLekuMX7puE4op75pNnEdh1M0AeWzkqLrBoZIaU2ynSrKN5VZg=="],
|
||||
|
||||
"expo-font": ["expo-font@55.0.6", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-x9czUA3UQWjIwa0ZUEs/eWJNqB4mAue/m4ltESlNPLZhHL0nWWqIfsyHmklTLFH7mVfcHSJvew6k+pR2FE1zVw=="],
|
||||
|
||||
"expo-glass-effect": ["expo-glass-effect@55.0.10", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-5kL/jATvgJWdrqPdxixrECJqD2l8cfQ4ALr1DK7qi9XkyI97ejXvUjB2VsfEePNy3Fg+/VwzA3n3L7Nv3tAPkw=="],
|
||||
|
||||
"expo-haptics": ["expo-haptics@55.0.14", "", { "peerDependencies": { "expo": "*" } }, "sha512-KjDItBsA9mi1f5nRwf8g1wOdfEcLHwvEdt5Jl1sMCDETR/homcGOl+F3QIiPOl/PRlbGVieQsjTtF4DGtHOj6g=="],
|
||||
|
||||
"expo-image": ["expo-image@55.0.8", "", { "dependencies": { "sf-symbols-typescript": "^2.2.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-fNdvdYVcGn3g1x6o5AXHKzk4xX8U6rg2W9vFdE1pQO80kWCNReh003ypqSrGy4dD+zA8FtZjrNF3oMDGnPpIGQ=="],
|
||||
|
||||
"expo-json-utils": ["expo-json-utils@55.0.2", "", {}, "sha512-QJMOZOPOG7CTnKcrdVaiummn2va1MCO56z++eyWkDv3GBRODldM6MFMDf/jTREWthFc2Nxo6TuyWRrEV9S6n/Q=="],
|
||||
|
||||
"expo-keep-awake": ["expo-keep-awake@55.0.6", "", { "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-acJjeHqkNxMVckEcJhGQeIksqqsarscSHJtT559bNgyiM4r14dViQ66su7bb6qDVeBt0K7z3glXI1dHVck1Zgg=="],
|
||||
|
||||
"expo-linking": ["expo-linking@55.0.13", "", { "dependencies": { "expo-constants": "~55.0.14", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-xbOqNWQCC5RGtXSW83ZCKOjRivyxO2zBouRYy/hgbsyrHUJhztMAjlq8RKYDUL8D6QVsH9Q81SNoq4Zhcn+4HQ=="],
|
||||
|
||||
"expo-manifests": ["expo-manifests@55.0.15", "", { "dependencies": { "@expo/config": "~55.0.14", "expo-json-utils": "~55.0.2" }, "peerDependencies": { "expo": "*" } }, "sha512-p40ftXpgLTFGddFy35MYZMyjm/E6IQdn3l6fBZZ6zeraEzYLt+VLHYsplOL9ccTYvUSWKN9aOWRpoEYpyGVBVw=="],
|
||||
|
||||
"expo-modules-autolinking": ["expo-modules-autolinking@55.0.17", "", { "dependencies": { "@expo/require-utils": "^55.0.4", "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "commander": "^7.2.0" }, "bin": { "expo-modules-autolinking": "bin/expo-modules-autolinking.js" } }, "sha512-VhlEVGnP+xBjfSKDKNN7GAPKN2whIfV08jsZvNj7UGyJWpZYiO6Emx1FLP5xd1+JZVpIrt/kxR641kdcPo7Ehw=="],
|
||||
|
||||
"expo-modules-core": ["expo-modules-core@55.0.22", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-worklets": "^0.7.4 || ^0.8.0" }, "optionalPeers": ["react-native-worklets"] }, "sha512-NC5GyvCHvnOvi5MtgLv68oUSrRP/0UORGzU/MX+7BIA8ctgBPxKSjPXPSfhwk3gMzj7eHBhYwlu0HJsIEnVd9A=="],
|
||||
|
||||
"expo-notifications": ["expo-notifications@55.0.19", "", { "dependencies": { "@expo/image-utils": "^0.8.13", "abort-controller": "^3.0.0", "badgin": "^1.1.5", "expo-application": "~55.0.14", "expo-constants": "~55.0.14" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-t1DPN9xwSh5kN5T8k0lER/NbcuhMIxj/ukfDWzFOJb2WdfB+VBWaiBUj4K26agsX7tQFMz2wsCXpkYXLdfzSdw=="],
|
||||
|
||||
"expo-router": ["expo-router@55.0.12", "", { "dependencies": { "@expo/metro-runtime": "^55.0.9", "@expo/schema-utils": "^55.0.3", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.12", "@react-navigation/bottom-tabs": "^7.15.5", "@react-navigation/native": "^7.1.33", "@react-navigation/native-stack": "^7.14.5", "client-only": "^0.0.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "expo-glass-effect": "^55.0.10", "expo-image": "^55.0.8", "expo-server": "^55.0.7", "expo-symbols": "^55.0.7", "fast-deep-equal": "^3.1.3", "invariant": "^2.2.4", "nanoid": "^3.3.8", "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-native-is-edge-to-edge": "^1.2.1", "semver": "~7.6.3", "server-only": "^0.0.1", "sf-symbols-typescript": "^2.1.0", "shallowequal": "^1.1.0", "use-latest-callback": "^0.2.1", "vaul": "^1.1.2" }, "peerDependencies": { "@expo/log-box": "55.0.10", "@react-navigation/drawer": "^7.9.4", "@testing-library/react-native": ">= 13.2.0", "expo": "*", "expo-constants": "^55.0.13", "expo-linking": "^55.0.12", "react": "*", "react-dom": "*", "react-native": "*", "react-native-gesture-handler": "*", "react-native-reanimated": "*", "react-native-safe-area-context": ">= 5.4.0", "react-native-screens": "*", "react-native-web": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "optionalPeers": ["@react-navigation/drawer", "@testing-library/react-native", "react-dom", "react-native-gesture-handler", "react-native-reanimated", "react-native-web", "react-server-dom-webpack"] }, "sha512-Bm6IhI0Kl5/tDlCHPms8jDqy1O6HLHIOrMsEmmAQ5Lgg5UBtDfRThEyHPVOLNTOs8e7/bG/Ftz6a4UgQVA+NhQ=="],
|
||||
|
||||
"expo-server": ["expo-server@55.0.7", "", {}, "sha512-Cc1btFyPsD9P4DT2xd1pG/uR96TLVMx0W+dPm9Gjk1uDV9xuzvMcUsY7nf9bt4U5pGyWWkCXmPJcKwWfdl51Pw=="],
|
||||
|
||||
"expo-splash-screen": ["expo-splash-screen@55.0.18", "", { "dependencies": { "@expo/prebuild-config": "^55.0.15" }, "peerDependencies": { "expo": "*" } }, "sha512-5+sA2L2e0v7GVWl2+j24lSNnC39HtycCCtJXHiC2N+voWLtZp0qMLAKZY/1vhkzjYzDzfkUcZiRzkdhwT9x+2Q=="],
|
||||
|
||||
"expo-status-bar": ["expo-status-bar@55.0.5", "", { "dependencies": { "react-native-is-edge-to-edge": "^1.2.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-qb0c3rJO2b7CC0gUVGi1JYp92oLenWdYGyk8l4YQs6U+uaXUTPv6aaFa3KkT2HON10re3AxxPNJci8rsz6kPxg=="],
|
||||
|
||||
"expo-structured-headers": ["expo-structured-headers@55.0.2", "", {}, "sha512-KITovrWigTOtsII5hRQ9/3ydaNcxCux5g6O+eTPLyjnye9dpkDKl5GmCLVPVKIL/d7253OtbGtWMD4m0gha5pw=="],
|
||||
|
||||
"expo-symbols": ["expo-symbols@55.0.7", "", { "dependencies": { "@expo-google-fonts/material-symbols": "^0.4.1", "sf-symbols-typescript": "^2.0.0" }, "peerDependencies": { "expo": "*", "expo-font": "*", "react": "*", "react-native": "*" } }, "sha512-y4ALLbncSGQzhFLw1PaIBbO39xzaw3ie249HmK6zK/WLJYfw4Z/9UU4iPKO3KCE4FyCKIzd+yRsvzvlri23YrQ=="],
|
||||
|
||||
"expo-system-ui": ["expo-system-ui@55.0.15", "", { "dependencies": { "@react-native/normalize-colors": "0.83.4", "debug": "^4.3.2" }, "peerDependencies": { "expo": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-hnpYpXgm1sXDb46yb7RB+Iq9z44wtDuFOYC8m2pfBNj3Dnv5xnh3CL2N4CpAbLZPwMSBrNk9Opkb9VFwn1FM6A=="],
|
||||
|
||||
"expo-task-manager": ["expo-task-manager@55.0.14", "", { "dependencies": { "unimodules-app-loader": "~55.0.4" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-KWee8OhusVJYkhCfFtZ1AqsjkbnTqErgcV595CY0mUQZK7Phhe1qJsv9xiIpxTI0nbOS/248nvm/FcVOrZPaPw=="],
|
||||
|
||||
"expo-updates": ["expo-updates@55.0.20", "", { "dependencies": { "@expo/code-signing-certificates": "^0.0.6", "@expo/plist": "^0.5.2", "@expo/spawn-async": "^1.7.2", "arg": "^4.1.0", "chalk": "^4.1.2", "debug": "^4.3.4", "expo-eas-client": "~55.0.5", "expo-manifests": "~55.0.15", "expo-structured-headers": "~55.0.2", "expo-updates-interface": "~55.1.5", "getenv": "^2.0.0", "glob": "^13.0.0", "ignore": "^5.3.1", "resolve-from": "^5.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" }, "bin": { "expo-updates": "bin/cli.js" } }, "sha512-bRVsm+2ax3rQkErV+YX9uw+2N5DJ7C2S4ETPZPFbnLubSCJtlPuMHZ2SDQvmh3mAOl0yURKkbIMWVCvT89GmIQ=="],
|
||||
|
||||
"expo-updates-interface": ["expo-updates-interface@55.1.5", "", { "peerDependencies": { "expo": "*" } }, "sha512-YOk9vhplWi0djoeqxMlEQgcDFeOGhnj4dWU0v1QvF5RqpqwLGdx780E0k3zL85xw6LXljVN78d6g8z51qIZu5g=="],
|
||||
|
||||
"expo-web-browser": ["expo-web-browser@55.0.14", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-bTDkBSQBnrlnYcM7Aak72AOvJuvdgA3M8p//Lazrm0Nfa77T9cRXzQ6KhLrB08V39n1+00d1dvuTWznJslkmdg=="],
|
||||
|
||||
"exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="],
|
||||
|
||||
"express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="],
|
||||
@@ -3194,6 +3742,8 @@
|
||||
|
||||
"fast-json-stringify": ["fast-json-stringify@6.3.0", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.2.0", "ajv": "^8.12.0", "ajv-formats": "^3.0.1", "fast-uri": "^3.0.0", "json-schema-ref-resolver": "^3.0.0", "rfdc": "^1.2.0" } }, "sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA=="],
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||
|
||||
"fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="],
|
||||
|
||||
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
|
||||
@@ -3208,18 +3758,32 @@
|
||||
|
||||
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
|
||||
|
||||
"fb-dotslash": ["fb-dotslash@0.5.8", "", { "bin": { "dotslash": "bin/dotslash" } }, "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA=="],
|
||||
|
||||
"fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="],
|
||||
|
||||
"fbjs": ["fbjs@3.0.5", "", { "dependencies": { "cross-fetch": "^3.1.5", "fbjs-css-vars": "^1.0.0", "loose-envify": "^1.0.0", "object-assign": "^4.1.0", "promise": "^7.1.1", "setimmediate": "^1.0.5", "ua-parser-js": "^1.0.35" } }, "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg=="],
|
||||
|
||||
"fbjs-css-vars": ["fbjs-css-vars@1.0.2", "", {}, "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="],
|
||||
|
||||
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
|
||||
|
||||
"fetch-nodeshim": ["fetch-nodeshim@0.4.10", "", {}, "sha512-m6I8ALe4L4XpdETy7MJZWs6L1IVMbjs99bwbpIKphxX+0CTns4IKDWJY0LWfr4YsFjfg+z1TjzTMU8lKl8rG0w=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
||||
|
||||
"file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
|
||||
|
||||
"filelist": ["filelist@1.0.6", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
"filter-obj": ["filter-obj@1.1.0", "", {}, "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="],
|
||||
|
||||
"finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="],
|
||||
|
||||
"find-babel-config": ["find-babel-config@2.1.2", "", { "dependencies": { "json5": "^2.2.3" } }, "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg=="],
|
||||
@@ -3230,16 +3794,24 @@
|
||||
|
||||
"find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="],
|
||||
|
||||
"find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
|
||||
"finity": ["finity@0.5.4", "", {}, "sha512-3l+5/1tuw616Lgb0QBimxfdd2TqaDGpfCBpfX6EqtFmqUV3FtQnVEX4Aa62DagYEqnsTIjZcTfbq9msDbXYgyA=="],
|
||||
|
||||
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
||||
|
||||
"flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
|
||||
|
||||
"flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="],
|
||||
|
||||
"flow-enums-runtime": ["flow-enums-runtime@0.0.6", "", {}, "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw=="],
|
||||
|
||||
"follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="],
|
||||
|
||||
"fontace": ["fontace@0.3.1", "", { "dependencies": { "@types/fontkit": "^2.0.8", "fontkit": "^2.0.4" } }, "sha512-9f5g4feWT1jWT8+SbL85aLIRLIXUaDygaM2xPXRmzPYxrOMNok79Lr3FGJoKVNKibE0WCunNiEVG2mwuE+2qEg=="],
|
||||
|
||||
"fontfaceobserver": ["fontfaceobserver@2.3.0", "", {}, "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg=="],
|
||||
|
||||
"fontkit": ["fontkit@2.0.4", "", { "dependencies": { "@swc/helpers": "^0.5.12", "brotli": "^1.3.2", "clone": "^2.1.2", "dfa": "^1.2.0", "fast-deep-equal": "^3.1.3", "restructure": "^3.0.0", "tiny-inflate": "^1.0.3", "unicode-properties": "^1.4.0", "unicode-trie": "^2.0.0" } }, "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g=="],
|
||||
|
||||
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
|
||||
@@ -3296,6 +3868,8 @@
|
||||
|
||||
"get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
|
||||
|
||||
"get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="],
|
||||
|
||||
"get-port": ["get-port@7.2.0", "", {}, "sha512-afP4W205ONCuMoPBqcR6PSXnzX35KTcJygfJfcp+QY+uwm3p20p1YczWXhlICIzGMCxYBQcySEcOgsJcrkyobg=="],
|
||||
|
||||
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||
@@ -3306,6 +3880,8 @@
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.13.8", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-J87BxkLXykmisLQ+KA4x2+O6rVf+PJrtFUO8lGyiRg4lyxJLJ8/v0sRAKdVZQOy6tR6lMRAF1NqzCf9BQijm0w=="],
|
||||
|
||||
"getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="],
|
||||
|
||||
"ghostty-web": ["ghostty-web@github:anomalyco/ghostty-web#20bd361", {}, "anomalyco-ghostty-web-20bd361", "sha512-dW0nwaiBBcun9y5WJSvm3HxDLe5o9V0xLCndQvWonRVubU8CS1PHxZpLffyPt1YujPWC13ez03aWxcuKBPYYGQ=="],
|
||||
|
||||
"gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
|
||||
@@ -3410,8 +3986,16 @@
|
||||
|
||||
"heap-snapshot-toolkit": ["heap-snapshot-toolkit@1.1.3", "", {}, "sha512-joThu2rEsDu8/l4arupRDI1qP4CZXNG+J6Wr348vnbLGSiBkwRdqZ6aOHl5BzEiC+Dc8OTbMlmWjD0lbXD5K2Q=="],
|
||||
|
||||
"hermes-compiler": ["hermes-compiler@0.14.1", "", {}, "sha512-+RPPQlayoZ9n6/KXKt5SFILWXCGJ/LV5d24L5smXrvTDrPS4L6dSctPczXauuvzFP3QEJbD1YO7Z3Ra4a+4IhA=="],
|
||||
|
||||
"hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
|
||||
|
||||
"hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
|
||||
|
||||
"hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="],
|
||||
|
||||
"hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
|
||||
|
||||
"hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="],
|
||||
|
||||
"hono-openapi": ["hono-openapi@1.1.2", "", { "peerDependencies": { "@hono/standard-validator": "^0.2.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-toUcO60MftRBxqcVyxsHNYs2m4vf4xkQaiARAucQx3TiBPDtMNNkoh+C4I1vAretQZiGyaLOZNWn1YxfSyUA5g=="],
|
||||
@@ -3450,6 +4034,8 @@
|
||||
|
||||
"husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="],
|
||||
|
||||
"hyphenate-style-name": ["hyphenate-style-name@1.1.0", "", {}, "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw=="],
|
||||
|
||||
"i18next": ["i18next@23.16.8", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg=="],
|
||||
|
||||
"iconv-corefoundation": ["iconv-corefoundation@1.1.7", "", { "dependencies": { "cli-truncate": "^2.1.0", "node-addon-api": "^1.6.3" }, "os": "darwin" }, "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ=="],
|
||||
@@ -3464,6 +4050,8 @@
|
||||
|
||||
"image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="],
|
||||
|
||||
"image-size": ["image-size@1.2.1", "", { "dependencies": { "queue": "6.0.2" }, "bin": { "image-size": "bin/image-size.js" } }, "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw=="],
|
||||
|
||||
"immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="],
|
||||
|
||||
"import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="],
|
||||
@@ -3482,8 +4070,12 @@
|
||||
|
||||
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
|
||||
|
||||
"inline-style-prefixer": ["inline-style-prefixer@7.0.1", "", { "dependencies": { "css-in-js-utils": "^3.1.0" } }, "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw=="],
|
||||
|
||||
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
|
||||
|
||||
"invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="],
|
||||
|
||||
"ioredis": ["ioredis@5.10.1", "", { "dependencies": { "@ioredis/commands": "1.5.1", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA=="],
|
||||
|
||||
"ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
|
||||
@@ -3598,6 +4190,10 @@
|
||||
|
||||
"isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="],
|
||||
|
||||
"istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
|
||||
|
||||
"istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="],
|
||||
|
||||
"iterate-iterator": ["iterate-iterator@1.0.2", "", {}, "sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw=="],
|
||||
|
||||
"iterate-value": ["iterate-value@1.0.2", "", { "dependencies": { "es-get-iterator": "^1.0.2", "iterate-iterator": "^1.0.1" } }, "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ=="],
|
||||
@@ -3606,8 +4202,28 @@
|
||||
|
||||
"jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="],
|
||||
|
||||
"jest-environment-node": ["jest-environment-node@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw=="],
|
||||
|
||||
"jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="],
|
||||
|
||||
"jest-haste-map": ["jest-haste-map@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA=="],
|
||||
|
||||
"jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="],
|
||||
|
||||
"jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="],
|
||||
|
||||
"jest-regex-util": ["jest-regex-util@29.6.3", "", {}, "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg=="],
|
||||
|
||||
"jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="],
|
||||
|
||||
"jest-validate": ["jest-validate@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", "pretty-format": "^29.7.0" } }, "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw=="],
|
||||
|
||||
"jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="],
|
||||
|
||||
"jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="],
|
||||
|
||||
"jimp-compact": ["jimp-compact@0.16.1", "", {}, "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww=="],
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"jmespath": ["jmespath@0.16.0", "", {}, "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw=="],
|
||||
@@ -3630,6 +4246,8 @@
|
||||
|
||||
"jsbi": ["jsbi@4.3.2", "", {}, "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew=="],
|
||||
|
||||
"jsc-safe-url": ["jsc-safe-url@0.2.4", "", {}, "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
|
||||
@@ -3648,6 +4266,8 @@
|
||||
|
||||
"json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
|
||||
|
||||
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
||||
|
||||
"json-stringify-nice": ["json-stringify-nice@1.1.4", "", {}, "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw=="],
|
||||
|
||||
"json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="],
|
||||
@@ -3686,6 +4306,8 @@
|
||||
|
||||
"kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="],
|
||||
|
||||
"lan-network": ["lan-network@0.2.1", "", { "bin": { "lan-network": "dist/lan-network-cli.js" } }, "sha512-ONPnazC96VKDntab9j9JKwIWhZ4ZUceB4A9Epu4Ssg0hYFmtHZSeQ+n15nIwTFmcBUKtExOer8WTJ4GF9MO64A=="],
|
||||
|
||||
"lang-map": ["lang-map@0.4.0", "", { "dependencies": { "language-map": "^1.1.0" } }, "sha512-oiSqZIEUnWdFeDNsp4HId4tAxdFbx5iMBOwA3666Fn2L8Khj8NiD9xRvMsGmKXopPVkaDFtSv3CJOmXFUB0Hcg=="],
|
||||
|
||||
"language-map": ["language-map@1.5.0", "", {}, "sha512-n7gFZpe+DwEAX9cXVTw43i3wiudWDDtSn28RmdnS/HCPr284dQI/SztsamWanRr75oSlKSaGbV2nmWCTzGCoVg=="],
|
||||
@@ -3696,8 +4318,14 @@
|
||||
|
||||
"leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="],
|
||||
|
||||
"leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"light-my-request": ["light-my-request@6.6.0", "", { "dependencies": { "cookie": "^1.0.1", "process-warning": "^4.0.0", "set-cookie-parser": "^2.6.0" } }, "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A=="],
|
||||
|
||||
"lighthouse-logger": ["lighthouse-logger@1.4.2", "", { "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" } }, "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
|
||||
@@ -3724,10 +4352,12 @@
|
||||
|
||||
"lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
|
||||
|
||||
"locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
|
||||
|
||||
"lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
|
||||
|
||||
"lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
|
||||
|
||||
"lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="],
|
||||
@@ -3750,7 +4380,9 @@
|
||||
|
||||
"lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
|
||||
|
||||
"log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
|
||||
"lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="],
|
||||
|
||||
"log-symbols": ["log-symbols@2.2.0", "", { "dependencies": { "chalk": "^2.0.1" } }, "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg=="],
|
||||
|
||||
"loglevelnext": ["loglevelnext@6.0.0", "", {}, "sha512-FDl1AI2sJGjHHG3XKJd6sG3/6ncgiGCQ0YkW46nxe7SfqQq6hujd9CvFXIXtkGBUN83KPZ2KSOJK8q5P0bSSRQ=="],
|
||||
|
||||
@@ -3782,6 +4414,8 @@
|
||||
|
||||
"make-fetch-happen": ["make-fetch-happen@15.0.5", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", "ssri": "^13.0.0" } }, "sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg=="],
|
||||
|
||||
"makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="],
|
||||
|
||||
"markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="],
|
||||
|
||||
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
|
||||
@@ -3792,6 +4426,8 @@
|
||||
|
||||
"marked-shiki": ["marked-shiki@1.2.1", "", { "peerDependencies": { "marked": ">=7.0.0", "shiki": ">=1.0.0" } }, "sha512-yHxYQhPY5oYaIRnROn98foKhuClark7M373/VpLxiy5TrDu9Jd/LsMwo8w+U91Up4oDb9IXFrP0N1MFRz8W/DQ=="],
|
||||
|
||||
"marky": ["marky@1.3.0", "", {}, "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ=="],
|
||||
|
||||
"matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
@@ -3838,6 +4474,8 @@
|
||||
|
||||
"media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
|
||||
|
||||
"memoize-one": ["memoize-one@5.2.1", "", {}, "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="],
|
||||
|
||||
"merge-anything": ["merge-anything@5.1.7", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ=="],
|
||||
|
||||
"merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="],
|
||||
@@ -3848,6 +4486,34 @@
|
||||
|
||||
"methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="],
|
||||
|
||||
"metro": ["metro@0.83.5", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "accepts": "^2.0.0", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.33.3", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.5", "metro-cache": "0.83.5", "metro-cache-key": "0.83.5", "metro-config": "0.83.5", "metro-core": "0.83.5", "metro-file-map": "0.83.5", "metro-resolver": "0.83.5", "metro-runtime": "0.83.5", "metro-source-map": "0.83.5", "metro-symbolicate": "0.83.5", "metro-transform-plugins": "0.83.5", "metro-transform-worker": "0.83.5", "mime-types": "^3.0.1", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-BgsXevY1MBac/3ZYv/RfNFf/4iuW9X7f4H8ZNkiH+r667HD9sVujxcmu4jvEzGCAm4/WyKdZCuyhAcyhTHOucQ=="],
|
||||
|
||||
"metro-babel-transformer": ["metro-babel-transformer@0.83.5", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.33.3", "nullthrows": "^1.1.1" } }, "sha512-d9FfmgUEVejTiSb7bkQeLRGl6aeno2UpuPm3bo3rCYwxewj03ymvOn8s8vnS4fBqAPQ+cE9iQM40wh7nGXR+eA=="],
|
||||
|
||||
"metro-cache": ["metro-cache@0.83.5", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.5" } }, "sha512-oH+s4U+IfZyg8J42bne2Skc90rcuESIYf86dYittcdWQtPfcaFXWpByPyTuWk3rR1Zz3Eh5HOrcVImfEhhJLng=="],
|
||||
|
||||
"metro-cache-key": ["metro-cache-key@0.83.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-Ycl8PBajB7bhbAI7Rt0xEyiF8oJ0RWX8EKkolV1KfCUlC++V/GStMSGpPLwnnBZXZWkCC5edBPzv1Hz1Yi0Euw=="],
|
||||
|
||||
"metro-config": ["metro-config@0.83.5", "", { "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.5", "metro-cache": "0.83.5", "metro-core": "0.83.5", "metro-runtime": "0.83.5", "yaml": "^2.6.1" } }, "sha512-JQ/PAASXH7yczgV6OCUSRhZYME+NU8NYjI2RcaG5ga4QfQ3T/XdiLzpSb3awWZYlDCcQb36l4Vl7i0Zw7/Tf9w=="],
|
||||
|
||||
"metro-core": ["metro-core@0.83.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.5" } }, "sha512-YcVcLCrf0ed4mdLa82Qob0VxYqfhmlRxUS8+TO4gosZo/gLwSvtdeOjc/Vt0pe/lvMNrBap9LlmvZM8FIsMgJQ=="],
|
||||
|
||||
"metro-file-map": ["metro-file-map@0.83.5", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-ZEt8s3a1cnYbn40nyCD+CsZdYSlwtFh2kFym4lo+uvfM+UMMH+r/BsrC6rbNClSrt+B7rU9T+Te/sh/NL8ZZKQ=="],
|
||||
|
||||
"metro-minify-terser": ["metro-minify-terser@0.83.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-Toe4Md1wS1PBqbvB0cFxBzKEVyyuYTUb0sgifAZh/mSvLH84qA1NAWik9sISWatzvfWf3rOGoUoO5E3f193a3Q=="],
|
||||
|
||||
"metro-resolver": ["metro-resolver@0.83.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-7p3GtzVUpbAweJeCcUJihJeOQl1bDuimO5ueo1K0BUpUtR41q5EilbQ3klt16UTPPMpA+tISWBtsrqU556mY1A=="],
|
||||
|
||||
"metro-runtime": ["metro-runtime@0.83.6", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-WQPua1G2VgYbwRn6vSKxOhTX7CFbSf/JdUu6Nd8bZnPXckOf7HQ2y51NXNQHoEsiuawathrkzL8pBhv+zgZFmg=="],
|
||||
|
||||
"metro-source-map": ["metro-source-map@0.83.6", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.6", "nullthrows": "^1.1.1", "ob1": "0.83.6", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-AqJbOMMpeyyM4iNI91pchqDIszzNuuHApEhg6OABqZ+9mjLEqzcIEQ/fboZ7x74fNU5DBd2K36FdUQYPqlGClA=="],
|
||||
|
||||
"metro-symbolicate": ["metro-symbolicate@0.83.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.5", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-EMIkrjNRz/hF+p0RDdxoE60+dkaTLPN3vaaGkFmX5lvFdO6HPfHA/Ywznzkev+za0VhPQ5KSdz49/MALBRteHA=="],
|
||||
|
||||
"metro-transform-plugins": ["metro-transform-plugins@0.83.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" } }, "sha512-KxYKzZL+lt3Os5H2nx7YkbkWVduLZL5kPrE/Yq+Prm/DE1VLhpfnO6HtPs8vimYFKOa58ncl60GpoX0h7Wm0Vw=="],
|
||||
|
||||
"metro-transform-worker": ["metro-transform-worker@0.83.5", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "metro": "0.83.5", "metro-babel-transformer": "0.83.5", "metro-cache": "0.83.5", "metro-cache-key": "0.83.5", "metro-minify-terser": "0.83.5", "metro-source-map": "0.83.5", "metro-transform-plugins": "0.83.5", "nullthrows": "^1.1.1" } }, "sha512-8N4pjkNXc6ytlP9oAM6MwqkvUepNSW39LKYl9NjUMpRDazBQ7oBpQDc8Sz4aI8jnH6AGhF7s1m/ayxkN1t04yA=="],
|
||||
|
||||
"micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
|
||||
|
||||
"micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
|
||||
@@ -3958,6 +4624,8 @@
|
||||
|
||||
"mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
|
||||
|
||||
"mobile-voice": ["mobile-voice@workspace:packages/mobile-voice"],
|
||||
|
||||
"morphdom": ["morphdom@2.7.8", "", {}, "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg=="],
|
||||
|
||||
"motion": ["motion@12.34.5", "", { "dependencies": { "framer-motion": "^12.34.5", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-N06NLJ9IeBHeielRqIvYvjPfXuRdyTxa+9++BgpGa+hY2D7TcMkI6QzV3jaRuv0aZRXgMa7cPy9YcBUBisPzAQ=="],
|
||||
@@ -3982,6 +4650,8 @@
|
||||
|
||||
"multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="],
|
||||
|
||||
"multitars": ["multitars@0.2.5", "", {}, "sha512-T/i4uZOzd4j2VnS28eAOJS0MgeAbcsFIijRPeLRhVv54hP9OqsC/FjYK0JmMTWxGhF2fv34oH1mtR6XLBKkNlw=="],
|
||||
|
||||
"mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="],
|
||||
|
||||
"mysql2": ["mysql2@3.14.4", "", { "dependencies": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", "generate-function": "^2.3.1", "iconv-lite": "^0.7.0", "long": "^5.2.1", "lru.min": "^1.0.0", "named-placeholders": "^1.1.3", "seq-queue": "^0.0.5", "sqlstring": "^2.3.2" } }, "sha512-Cs/jx3WZPNrYHVz+Iunp9ziahaG5uFMvD2R8Zlmc194AqXNxt9HBNu7ZsPYrUtmJsF0egETCWIdMIYAwOGjL1w=="],
|
||||
@@ -3996,6 +4666,8 @@
|
||||
|
||||
"native-duplexpair": ["native-duplexpair@1.0.0", "", {}, "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||
|
||||
"neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
|
||||
@@ -4020,6 +4692,8 @@
|
||||
|
||||
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||
|
||||
"node-forge": ["node-forge@1.4.0", "", {}, "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ=="],
|
||||
|
||||
"node-gyp": ["node-gyp@12.2.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^15.0.0", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ=="],
|
||||
|
||||
"node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
|
||||
@@ -4028,6 +4702,8 @@
|
||||
|
||||
"node-html-parser": ["node-html-parser@7.1.0", "", { "dependencies": { "css-select": "^5.1.0", "he": "1.2.0" } }, "sha512-iJo8b2uYGT40Y8BTyy5ufL6IVbN8rbm/1QK2xffXU/1a/v3AAa0d1YAoqBNYqaS4R/HajkWIpIfdE6KcyFh1AQ=="],
|
||||
|
||||
"node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
|
||||
|
||||
"node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.37", "", {}, "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg=="],
|
||||
@@ -4056,8 +4732,12 @@
|
||||
|
||||
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
|
||||
|
||||
"nullthrows": ["nullthrows@1.1.1", "", {}, "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw=="],
|
||||
|
||||
"nypm": ["nypm@0.6.5", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ=="],
|
||||
|
||||
"ob1": ["ob1@0.83.6", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-m/xZYkwcjo6UqLMrUICEB3iHk7Bjt3RSR7KXMi6Y1MO/kGkPhoRmfUDF6KAan3rLAZ7ABRqnQyKUTwaqZgUV4w=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
|
||||
"object-hash": ["object-hash@2.2.0", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="],
|
||||
@@ -4082,6 +4762,8 @@
|
||||
|
||||
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
||||
|
||||
"on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="],
|
||||
|
||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
||||
"onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="],
|
||||
@@ -4108,7 +4790,9 @@
|
||||
|
||||
"opentui-spinner": ["opentui-spinner@0.0.6", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.1.49", "@opentui/react": "^0.1.49", "@opentui/solid": "^0.1.49", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-xupLOeVQEAXEvVJCvHkfX6fChDWmJIPHe5jyUrVb8+n4XVTX8mBNhitFfB9v2ZbkC1H2UwPab/ElePHoW37NcA=="],
|
||||
|
||||
"ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="],
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
"ora": ["ora@3.4.0", "", { "dependencies": { "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-spinners": "^2.0.0", "log-symbols": "^2.2.0", "strip-ansi": "^5.2.0", "wcwidth": "^1.0.1" } }, "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg=="],
|
||||
|
||||
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
|
||||
|
||||
@@ -4128,7 +4812,7 @@
|
||||
|
||||
"p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="],
|
||||
|
||||
"p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"p-map": ["p-map@7.0.4", "", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="],
|
||||
|
||||
@@ -4164,6 +4848,8 @@
|
||||
|
||||
"parse-latin": ["parse-latin@7.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "@types/unist": "^3.0.0", "nlcst-to-string": "^4.0.0", "unist-util-modify-children": "^4.0.0", "unist-util-visit-children": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="],
|
||||
|
||||
"parse-png": ["parse-png@2.1.0", "", { "dependencies": { "pngjs": "^3.3.0" } }, "sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ=="],
|
||||
|
||||
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||
|
||||
"parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="],
|
||||
@@ -4242,7 +4928,7 @@
|
||||
|
||||
"plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="],
|
||||
|
||||
"pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
|
||||
"pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="],
|
||||
|
||||
"poe-oauth": ["poe-oauth@0.0.6", "", {}, "sha512-dI8xrVl7RSFh0B+cb4GGuCjIfGtDT9VpbpVkP0UKcunpXF0eFw+6GencoJ7k+E02ZYqopBQApMVWGq70/GP69w=="],
|
||||
|
||||
@@ -4270,11 +4956,13 @@
|
||||
|
||||
"powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
|
||||
|
||||
"pretty": ["pretty@2.0.0", "", { "dependencies": { "condense-newlines": "^0.2.1", "extend-shallow": "^2.0.1", "js-beautify": "^1.6.12" } }, "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w=="],
|
||||
|
||||
"pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
|
||||
"pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
|
||||
|
||||
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
|
||||
|
||||
@@ -4290,6 +4978,8 @@
|
||||
|
||||
"progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
|
||||
|
||||
"promise": ["promise@8.3.0", "", { "dependencies": { "asap": "~2.0.6" } }, "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg=="],
|
||||
|
||||
"promise-all-reject-late": ["promise-all-reject-late@1.0.1", "", {}, "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw=="],
|
||||
|
||||
"promise-call-limit": ["promise-call-limit@3.0.2", "", {}, "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw=="],
|
||||
@@ -4320,12 +5010,18 @@
|
||||
|
||||
"pure-rand": ["pure-rand@8.4.0", "", {}, "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A=="],
|
||||
|
||||
"qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="],
|
||||
|
||||
"qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="],
|
||||
|
||||
"quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
|
||||
|
||||
"query-string": ["query-string@7.1.3", "", { "dependencies": { "decode-uri-component": "^0.2.2", "filter-obj": "^1.1.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" } }, "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg=="],
|
||||
|
||||
"querystring": ["querystring@0.2.0", "", {}, "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g=="],
|
||||
|
||||
"queue": ["queue@6.0.2", "", { "dependencies": { "inherits": "~2.0.3" } }, "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
|
||||
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
|
||||
@@ -4342,11 +5038,37 @@
|
||||
|
||||
"react": ["react@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="],
|
||||
|
||||
"react-devtools-core": ["react-devtools-core@6.1.5", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA=="],
|
||||
|
||||
"react-docgen-typescript": ["react-docgen-typescript@2.4.0", "", { "peerDependencies": { "typescript": ">= 4.3.x" } }, "sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg=="],
|
||||
|
||||
"react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
|
||||
|
||||
"react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
||||
"react-fast-compare": ["react-fast-compare@3.2.2", "", {}, "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="],
|
||||
|
||||
"react-freeze": ["react-freeze@1.0.4", "", { "peerDependencies": { "react": ">=17.0.0" } }, "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA=="],
|
||||
|
||||
"react-is": ["react-is@19.2.5", "", {}, "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ=="],
|
||||
|
||||
"react-native": ["react-native@0.83.4", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.83.4", "@react-native/codegen": "0.83.4", "@react-native/community-cli-plugin": "0.83.4", "@react-native/gradle-plugin": "0.83.4", "@react-native/js-polyfills": "0.83.4", "@react-native/normalize-colors": "0.83.4", "@react-native/virtualized-lists": "0.83.4", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.32.0", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "hermes-compiler": "0.14.1", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.83.3", "metro-source-map": "^0.83.3", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.1.1", "react": "^19.2.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-H5Wco3UJyY6zZsjoBayY8RM9uiAEQ3FeG4G2NAt+lr9DO43QeqPlVe9xxxYEukMkEmeIhNjR70F6bhXuWArOMQ=="],
|
||||
|
||||
"react-native-audio-api": ["react-native-audio-api@0.11.7", "", { "dependencies": { "semver": "^7.7.3" }, "peerDependencies": { "react": "*", "react-native": "*" }, "bin": { "setup-rn-audio-api-web": "scripts/setup-rn-audio-api-web.js" } }, "sha512-2oIoP77Tn2nlouRVfEC3bAsuSyKU6xhGNkSnVXTLLQQZslEDoYX2cN9pVRZoWOqhFrLT8q4IZI9HaFgYL13L1A=="],
|
||||
|
||||
"react-native-gesture-handler": ["react-native-gesture-handler@2.30.1", "", { "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-xIUBDo5ktmJs++0fZlavQNvDEE4PsihWhSeJsJtoz4Q6p0MiTM9TgrTgfEgzRR36qGPytFoeq+ShLrVwGdpUdA=="],
|
||||
|
||||
"react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.3.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA=="],
|
||||
|
||||
"react-native-reanimated": ["react-native-reanimated@4.2.1", "", { "dependencies": { "react-native-is-edge-to-edge": "1.2.1", "semver": "7.7.3" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-worklets": ">=0.7.0" } }, "sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg=="],
|
||||
|
||||
"react-native-safe-area-context": ["react-native-safe-area-context@5.6.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg=="],
|
||||
|
||||
"react-native-screens": ["react-native-screens@4.23.0", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-XhO3aK0UeLpBn4kLecd+J+EDeRRJlI/Ro9Fze06vo1q163VeYtzfU9QS09/VyDFMWR1qxDC1iazCArTPSFFiPw=="],
|
||||
|
||||
"react-native-web": ["react-native-web@0.21.2", "", { "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", "fbjs": "^3.0.4", "inline-style-prefixer": "^7.0.1", "memoize-one": "^6.0.0", "nullthrows": "^1.1.1", "postcss-value-parser": "^4.2.0", "styleq": "^0.1.3" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg=="],
|
||||
|
||||
"react-native-worklets": ["react-native-worklets@0.7.2", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "7.27.1", "@babel/plugin-transform-class-properties": "7.27.1", "@babel/plugin-transform-classes": "7.28.4", "@babel/plugin-transform-nullish-coalescing-operator": "7.27.1", "@babel/plugin-transform-optional-chaining": "7.27.1", "@babel/plugin-transform-shorthand-properties": "7.27.1", "@babel/plugin-transform-template-literals": "7.27.1", "@babel/plugin-transform-unicode-regex": "7.27.1", "@babel/preset-typescript": "7.27.1", "convert-source-map": "2.0.0", "semver": "7.7.3" }, "peerDependencies": { "@babel/core": "*", "react": "*", "react-native": "*" } }, "sha512-DuLu1kMV/Uyl9pQHp3hehAlThoLw7Yk2FwRTpzASOmI+cd4845FWn3m2bk9MnjUw8FBRIyhwLqYm2AJaXDXsog=="],
|
||||
|
||||
"react-native-zeroconf": ["react-native-zeroconf@0.14.0", "", { "dependencies": { "events": "^3.0.0" }, "peerDependencies": { "react-native": ">=0.60" } }, "sha512-TqjORroaVZrBYLzk3YtviQy8lUl/iiMacknxixRYlmGaqgsv4LJXIYafpnvPa3y2SC4/qu2mvF8D1/VTTxylgQ=="],
|
||||
|
||||
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
|
||||
|
||||
@@ -4394,6 +5116,12 @@
|
||||
|
||||
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
|
||||
|
||||
"regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="],
|
||||
|
||||
"regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="],
|
||||
|
||||
"regenerator-runtime": ["regenerator-runtime@0.13.11", "", {}, "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="],
|
||||
|
||||
"regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
|
||||
|
||||
"regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="],
|
||||
@@ -4402,6 +5130,12 @@
|
||||
|
||||
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
||||
|
||||
"regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="],
|
||||
|
||||
"regjsgen": ["regjsgen@0.8.0", "", {}, "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q=="],
|
||||
|
||||
"regjsparser": ["regjsparser@0.13.1", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw=="],
|
||||
|
||||
"rehype": ["rehype@13.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "rehype-parse": "^9.0.0", "rehype-stringify": "^10.0.0", "unified": "^11.0.0" } }, "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A=="],
|
||||
|
||||
"rehype-autolink-headings": ["rehype-autolink-headings@7.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-heading-rank": "^3.0.0", "hast-util-is-element": "^3.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw=="],
|
||||
@@ -4444,6 +5178,8 @@
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="],
|
||||
|
||||
"resedit": ["resedit@1.7.2", "", { "dependencies": { "pe-library": "^0.4.1" } }, "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA=="],
|
||||
|
||||
"reselect": ["reselect@4.1.8", "", {}, "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="],
|
||||
@@ -4458,9 +5194,11 @@
|
||||
|
||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||
|
||||
"resolve-workspace-root": ["resolve-workspace-root@2.0.1", "", {}, "sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w=="],
|
||||
|
||||
"responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="],
|
||||
|
||||
"restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
|
||||
"restore-cursor": ["restore-cursor@2.0.0", "", { "dependencies": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" } }, "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q=="],
|
||||
|
||||
"restructure": ["restructure@3.0.2", "", {}, "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="],
|
||||
|
||||
@@ -4480,7 +5218,7 @@
|
||||
|
||||
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
||||
|
||||
"rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="],
|
||||
"rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
|
||||
|
||||
"roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],
|
||||
|
||||
@@ -4514,7 +5252,7 @@
|
||||
|
||||
"sax": ["sax@1.2.1", "", {}, "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA=="],
|
||||
|
||||
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
|
||||
"section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="],
|
||||
|
||||
@@ -4538,6 +5276,10 @@
|
||||
|
||||
"serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="],
|
||||
|
||||
"server-only": ["server-only@0.0.1", "", {}, "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA=="],
|
||||
|
||||
"set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="],
|
||||
|
||||
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
|
||||
|
||||
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
||||
@@ -4546,8 +5288,14 @@
|
||||
|
||||
"set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
|
||||
|
||||
"setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
|
||||
|
||||
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
||||
|
||||
"sf-symbols-typescript": ["sf-symbols-typescript@2.2.0", "", {}, "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw=="],
|
||||
|
||||
"shallowequal": ["shallowequal@1.1.0", "", {}, "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="],
|
||||
|
||||
"sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
@@ -4574,6 +5322,8 @@
|
||||
|
||||
"sigstore": ["sigstore@4.1.0", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0", "@sigstore/sign": "^4.1.0", "@sigstore/tuf": "^4.0.1", "@sigstore/verify": "^3.1.0" } }, "sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA=="],
|
||||
|
||||
"simple-plist": ["simple-plist@1.3.1", "", { "dependencies": { "bplist-creator": "0.1.0", "bplist-parser": "0.3.1", "plist": "^3.0.5" } }, "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw=="],
|
||||
|
||||
"simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="],
|
||||
|
||||
"simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="],
|
||||
@@ -4588,6 +5338,8 @@
|
||||
|
||||
"slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="],
|
||||
|
||||
"slugify": ["slugify@1.6.9", "", {}, "sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg=="],
|
||||
|
||||
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
|
||||
|
||||
"smol-toml": ["smol-toml@1.6.1", "", {}, "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg=="],
|
||||
@@ -4634,6 +5386,8 @@
|
||||
|
||||
"spdx-license-ids": ["spdx-license-ids@3.0.23", "", {}, "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw=="],
|
||||
|
||||
"split-on-first": ["split-on-first@1.1.0", "", {}, "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="],
|
||||
|
||||
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||
|
||||
"sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
|
||||
@@ -4662,10 +5416,14 @@
|
||||
|
||||
"sst-win32-x86": ["sst-win32-x86@3.18.10", "", { "os": "win32", "cpu": "none" }, "sha512-pq8SmV0pIjBFMY6DraUZ4akyTxHnfjIKCRbBLdMxFUZK8TzA1NK2YdjRt1AwrgXRYGRyctrz/mt4WyO0SMOVQQ=="],
|
||||
|
||||
"stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
|
||||
|
||||
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||
|
||||
"stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="],
|
||||
|
||||
"stacktrace-parser": ["stacktrace-parser@0.1.11", "", { "dependencies": { "type-fest": "^0.7.1" } }, "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg=="],
|
||||
|
||||
"stage-js": ["stage-js@1.0.2", "", {}, "sha512-EWTRBYlg7Qv9wGUao99/PfRe3KaiQqWmgSvTOXvaWnu1Jk/q/vV8yJVu6bi/3EqDZeMVnCPAjheba6OFc5k1GQ=="],
|
||||
|
||||
"standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
|
||||
@@ -4684,10 +5442,14 @@
|
||||
|
||||
"storybook-solidjs-vite": ["storybook-solidjs-vite@10.0.12", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.7.0", "@storybook/builder-vite": "^10.3.1", "@storybook/global": "^5.0.0", "vite-plugin-solid": "^2.11.11" }, "peerDependencies": { "solid-js": "^1.9.0", "storybook": "^0.0.0-0 || ^10.0.0", "typescript": "^4.0.0 || ^5.0.0 || ^6.0.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["typescript"] }, "sha512-KfKhJRdxbhFLHkBzLKSEk5sO2M/+KV9cdpki5Xdl5pwNP8kcoQnZ3b/okZk8dMRV6x19j86bKc7zDfc5bPSMwA=="],
|
||||
|
||||
"stream-buffers": ["stream-buffers@2.2.0", "", {}, "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg=="],
|
||||
|
||||
"stream-replace-string": ["stream-replace-string@2.0.0", "", {}, "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w=="],
|
||||
|
||||
"streamx": ["streamx@2.25.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg=="],
|
||||
|
||||
"strict-uri-encode": ["strict-uri-encode@2.0.0", "", {}, "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="],
|
||||
|
||||
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
@@ -4718,6 +5480,8 @@
|
||||
|
||||
"strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="],
|
||||
|
||||
"structured-headers": ["structured-headers@0.4.1", "", {}, "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg=="],
|
||||
|
||||
"stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="],
|
||||
|
||||
"stubborn-utils": ["stubborn-utils@1.0.2", "", {}, "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg=="],
|
||||
@@ -4726,6 +5490,8 @@
|
||||
|
||||
"style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
|
||||
|
||||
"styleq": ["styleq@0.1.3", "", {}, "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA=="],
|
||||
|
||||
"sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="],
|
||||
|
||||
"sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="],
|
||||
@@ -4734,12 +5500,16 @@
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"supports-hyperlinks": ["supports-hyperlinks@2.3.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
|
||||
"sury": ["sury@11.0.0-alpha.4", "", { "peerDependencies": { "rescript": "12.x" }, "optionalPeers": ["rescript"] }, "sha512-oeG/GJWZvQCKtGPpLbu0yCZudfr5LxycDo5kh7SJmKHDPCsEPJssIZL2Eb4Tl7g9aPEvIDuRrkS+L0pybsMEMA=="],
|
||||
|
||||
"system-architecture": ["system-architecture@0.1.0", "", {}, "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA=="],
|
||||
|
||||
"tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="],
|
||||
|
||||
"tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="],
|
||||
@@ -4758,10 +5528,14 @@
|
||||
|
||||
"temp-file": ["temp-file@3.4.0", "", { "dependencies": { "async-exit-hook": "^2.0.1", "fs-extra": "^10.0.0" } }, "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg=="],
|
||||
|
||||
"terminal-link": ["terminal-link@2.1.1", "", { "dependencies": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" } }, "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ=="],
|
||||
|
||||
"terracotta": ["terracotta@1.1.0", "", { "dependencies": { "solid-use": "^0.9.1" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-kfQciWUBUBgYkXu7gh3CK3FAJng/iqZslAaY08C+k1Hdx17aVEpcFFb/WPaysxAfcupNH3y53s/pc53xxZauww=="],
|
||||
|
||||
"terser": ["terser@5.46.1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ=="],
|
||||
|
||||
"test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="],
|
||||
|
||||
"text-decoder": ["text-decoder@1.2.7", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ=="],
|
||||
|
||||
"thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
|
||||
@@ -4772,6 +5546,8 @@
|
||||
|
||||
"three": ["three@0.177.0", "", {}, "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg=="],
|
||||
|
||||
"throat": ["throat@5.0.0", "", {}, "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA=="],
|
||||
|
||||
"thunky": ["thunky@1.1.0", "", {}, "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA=="],
|
||||
|
||||
"tiny-async-pool": ["tiny-async-pool@1.3.0", "", { "dependencies": { "semver": "^5.5.0" } }, "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA=="],
|
||||
@@ -4800,6 +5576,8 @@
|
||||
|
||||
"tmp-promise": ["tmp-promise@3.0.3", "", { "dependencies": { "tmp": "^0.2.0" } }, "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ=="],
|
||||
|
||||
"tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"toad-cache": ["toad-cache@3.7.0", "", {}, "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw=="],
|
||||
@@ -4812,6 +5590,8 @@
|
||||
|
||||
"toolbeam-docs-theme": ["toolbeam-docs-theme@0.4.8", "", { "peerDependencies": { "@astrojs/starlight": "^0.34.3", "astro": "^5.7.13" } }, "sha512-b+5ynEFp4Woe5a22hzNQm42lD23t13ZMihVxHbzjA50zdcM9aOSJTIjdJ0PDSd4/50HbBXcpHiQsz6rM4N88ww=="],
|
||||
|
||||
"toqr": ["toqr@0.1.1", "", {}, "sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA=="],
|
||||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
"traverse": ["traverse@0.3.9", "", {}, "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ=="],
|
||||
@@ -4830,6 +5610,8 @@
|
||||
|
||||
"ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||
|
||||
"ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="],
|
||||
|
||||
"ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
|
||||
@@ -4862,6 +5644,10 @@
|
||||
|
||||
"tw-to-css": ["tw-to-css@0.0.12", "", { "dependencies": { "postcss": "8.4.31", "postcss-css-variables": "0.18.0", "tailwindcss": "3.3.2" } }, "sha512-rQAsQvOtV1lBkyCw+iypMygNHrShYAItES5r8fMsrhhaj5qrV2LkZyXc8ccEH+u5bFjHjQ9iuxe90I7Kykf6pw=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="],
|
||||
|
||||
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
|
||||
"type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
|
||||
@@ -4880,6 +5666,8 @@
|
||||
|
||||
"typescript-auto-import-cache": ["typescript-auto-import-cache@0.3.6", "", { "dependencies": { "semver": "^7.3.8" } }, "sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ=="],
|
||||
|
||||
"ua-parser-js": ["ua-parser-js@0.7.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg=="],
|
||||
|
||||
"ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
|
||||
|
||||
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
|
||||
@@ -4898,14 +5686,24 @@
|
||||
|
||||
"unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
|
||||
|
||||
"unicode-canonical-property-names-ecmascript": ["unicode-canonical-property-names-ecmascript@2.0.1", "", {}, "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg=="],
|
||||
|
||||
"unicode-match-property-ecmascript": ["unicode-match-property-ecmascript@2.0.0", "", { "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" } }, "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q=="],
|
||||
|
||||
"unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="],
|
||||
|
||||
"unicode-properties": ["unicode-properties@1.4.1", "", { "dependencies": { "base64-js": "^1.3.0", "unicode-trie": "^2.0.0" } }, "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg=="],
|
||||
|
||||
"unicode-property-aliases-ecmascript": ["unicode-property-aliases-ecmascript@2.2.0", "", {}, "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ=="],
|
||||
|
||||
"unicode-trie": ["unicode-trie@2.0.0", "", { "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" } }, "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ=="],
|
||||
|
||||
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
|
||||
"unifont": ["unifont@0.5.2", "", { "dependencies": { "css-tree": "^3.0.0", "ofetch": "^1.4.1", "ohash": "^2.0.0" } }, "sha512-LzR4WUqzH9ILFvjLAUU7dK3Lnou/qd5kD+IakBtBK4S15/+x2y9VX+DcWQv6s551R6W+vzwgVS6tFg3XggGBgg=="],
|
||||
|
||||
"unimodules-app-loader": ["unimodules-app-loader@55.0.4", "", {}, "sha512-l3vMWR/lYLTj3JE4rhIX5vDVMDY9nGS550XaB90HENqUQnBEMdhhOpI8tOA37QJOUZE6pCQGeQX6mIdEu3uqzg=="],
|
||||
|
||||
"unique-filename": ["unique-filename@4.0.0", "", { "dependencies": { "unique-slug": "^5.0.0" } }, "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ=="],
|
||||
|
||||
"unique-slug": ["unique-slug@5.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg=="],
|
||||
@@ -4954,6 +5752,8 @@
|
||||
|
||||
"use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
|
||||
|
||||
"use-latest-callback": ["use-latest-callback@0.2.6", "", { "peerDependencies": { "react": ">=16.8" } }, "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg=="],
|
||||
|
||||
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
|
||||
|
||||
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
|
||||
@@ -4976,6 +5776,8 @@
|
||||
|
||||
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||
|
||||
"vaul": ["vaul@1.1.2", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA=="],
|
||||
|
||||
"venice-ai-sdk-provider": ["venice-ai-sdk-provider@2.0.1", "", { "dependencies": { "@ai-sdk/openai-compatible": "^2.0.37", "@ai-sdk/provider": "^3.0.8", "@ai-sdk/provider-utils": "^4.0.21" }, "peerDependencies": { "ai": "^6.0.90" } }, "sha512-6SxA8a4MoA6Q/c+D3q7My0Hfog76enN3n0MXhwosM+tso66rXBEGeBRD/0lravRDVzL2Q1w5QJPc86rAVJtfXg=="],
|
||||
|
||||
"verror": ["verror@1.10.1", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg=="],
|
||||
@@ -5000,6 +5802,8 @@
|
||||
|
||||
"vitest": ["vitest@4.1.4", "", { "dependencies": { "@vitest/expect": "4.1.4", "@vitest/mocker": "4.1.4", "@vitest/pretty-format": "4.1.4", "@vitest/runner": "4.1.4", "@vitest/snapshot": "4.1.4", "@vitest/spy": "4.1.4", "@vitest/utils": "4.1.4", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.4", "@vitest/browser-preview": "4.1.4", "@vitest/browser-webdriverio": "4.1.4", "@vitest/coverage-istanbul": "4.1.4", "@vitest/coverage-v8": "4.1.4", "@vitest/ui": "4.1.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg=="],
|
||||
|
||||
"vlq": ["vlq@1.0.1", "", {}, "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w=="],
|
||||
|
||||
"volar-service-css": ["volar-service-css@0.0.70", "", { "dependencies": { "vscode-css-languageservice": "^6.3.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-K1qyOvBpE3rzdAv3e4/6Rv5yizrYPy5R/ne3IWCAzLBuMO4qBMV3kSqWzj6KUVe6S0AnN6wxF7cRkiaKfYMYJw=="],
|
||||
|
||||
"volar-service-emmet": ["volar-service-emmet@0.0.70", "", { "dependencies": { "@emmetio/css-parser": "^0.4.1", "@emmetio/html-matcher": "^1.3.0", "@vscode/emmet-helper": "^2.9.3", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-xi5bC4m/VyE3zy/n2CXspKeDZs3qA41tHLTw275/7dNWM/RqE2z3BnDICQybHIVp/6G1iOQj5c1qXMgQC08TNg=="],
|
||||
@@ -5036,6 +5840,10 @@
|
||||
|
||||
"walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="],
|
||||
|
||||
"walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="],
|
||||
|
||||
"warn-once": ["warn-once@0.1.1", "", {}, "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q=="],
|
||||
|
||||
"wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
|
||||
|
||||
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
|
||||
@@ -5048,10 +5856,14 @@
|
||||
|
||||
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
||||
|
||||
"whatwg-fetch": ["whatwg-fetch@3.6.20", "", {}, "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="],
|
||||
|
||||
"whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
|
||||
|
||||
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||
|
||||
"whatwg-url-minimum": ["whatwg-url-minimum@0.1.1", "", {}, "sha512-u2FNVjFVFZhdjb502KzXy1gKn1mEisQRJssmSJT8CPhZdZa0AP6VCbWlXERKyGu0l09t0k50FiDiralpGhBxgA=="],
|
||||
|
||||
"when-exit": ["when-exit@2.1.5", "", {}, "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg=="],
|
||||
|
||||
"which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="],
|
||||
@@ -5062,14 +5874,20 @@
|
||||
|
||||
"which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
|
||||
|
||||
"which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="],
|
||||
|
||||
"which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="],
|
||||
|
||||
"which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="],
|
||||
|
||||
"whisper.rn": ["whisper.rn@0.5.5", "", { "dependencies": { "safe-buffer": "^5.2.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-awFE+ImMtRdGhA+hjm3GEwnSvyEVP1sdhMb+MyCa5bVdoOCpaxrwVwXDo9U46Qwkhwml3PCFaauTsGmRkTyhdw=="],
|
||||
|
||||
"why-is-node-running": ["why-is-node-running@3.2.2", "", { "bin": { "why-is-node-running": "cli.js" } }, "sha512-NKUzAelcoCXhXL4dJzKIwXeR8iEVqsA0Lq6Vnd0UXvgaKbzVo4ZTHROF2Jidrv+SgxOQ03fMinnNhzZATxOD3A=="],
|
||||
|
||||
"widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"workerd": ["workerd@1.20251118.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251118.0", "@cloudflare/workerd-darwin-arm64": "1.20251118.0", "@cloudflare/workerd-linux-64": "1.20251118.0", "@cloudflare/workerd-linux-arm64": "1.20251118.0", "@cloudflare/workerd-windows-64": "1.20251118.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-Om5ns0Lyx/LKtYI04IV0bjIrkBgoFNg0p6urzr2asekJlfP18RqFzyqMFZKf0i9Gnjtz/JfAS/Ol6tjCe5JJsQ=="],
|
||||
|
||||
"wrangler": ["wrangler@4.50.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.11", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251118.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20251118.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251118.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-+nuZuHZxDdKmAyXOSrHlciGshCoAPiy5dM+t6mEohWm7HpXvTHmWQGUf/na9jjWlWJHCJYOWzkA1P5HBJqrIEA=="],
|
||||
@@ -5086,6 +5904,8 @@
|
||||
|
||||
"wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
|
||||
|
||||
"xcode": ["xcode@3.0.1", "", { "dependencies": { "simple-plist": "^1.1.0", "uuid": "^7.0.3" } }, "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA=="],
|
||||
|
||||
"xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="],
|
||||
|
||||
"xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="],
|
||||
@@ -5134,8 +5954,12 @@
|
||||
|
||||
"zod-to-ts": ["zod-to-ts@1.2.0", "", { "peerDependencies": { "typescript": "^4.9.4 || ^5.0.2", "zod": "^3" } }, "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA=="],
|
||||
|
||||
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
|
||||
|
||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||
|
||||
"zxing-wasm": ["zxing-wasm@3.0.2", "", { "dependencies": { "@types/emscripten": "^1.41.5", "type-fest": "^5.5.0" } }, "sha512-2YMAriaYHX9wrBY2k7H0epSo+dyCaCZg/vOtt+nEDXM9ul480gkXz/9SkwpOeHcD2H5qqDG8lWDSBwpTcZpa6w=="],
|
||||
|
||||
"@actions/artifact/@actions/core": ["@actions/core@2.0.3", "", { "dependencies": { "@actions/exec": "^2.0.0", "@actions/http-client": "^3.0.2" } }, "sha512-Od9Thc3T1mQJYddvVPM4QGiLUewdh+3txmDYHHxoNdkqysR1MbCT+rFOtNUxYAz+7+6RIsqipVahY2GJqGPyxA=="],
|
||||
|
||||
"@actions/core/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="],
|
||||
@@ -5392,6 +6216,10 @@
|
||||
|
||||
"@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@babel/helper-create-regexp-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@babel/plugin-transform-runtime/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"@bufbuild/protoplugin/typescript": ["typescript@5.4.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ=="],
|
||||
|
||||
"@cloudflare/kv-asset-handler/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
|
||||
@@ -5428,6 +6256,8 @@
|
||||
|
||||
"@electron/rebuild/node-gyp": ["node-gyp@11.5.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ=="],
|
||||
|
||||
"@electron/rebuild/ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="],
|
||||
|
||||
"@electron/rebuild/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"@electron/universal/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="],
|
||||
@@ -5436,6 +6266,62 @@
|
||||
|
||||
"@electron/windows-sign/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@expo/cli/arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
|
||||
|
||||
"@expo/cli/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"@expo/cli/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
|
||||
|
||||
"@expo/cli/npm-package-arg": ["npm-package-arg@11.0.3", "", { "dependencies": { "hosted-git-info": "^7.0.0", "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" } }, "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw=="],
|
||||
|
||||
"@expo/cli/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"@expo/cli/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="],
|
||||
|
||||
"@expo/cli/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@expo/config-plugins/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"@expo/config-plugins/xml2js": ["xml2js@0.6.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w=="],
|
||||
|
||||
"@expo/devcert/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
|
||||
"@expo/devtools/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"@expo/env/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"@expo/fingerprint/arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
|
||||
|
||||
"@expo/fingerprint/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"@expo/fingerprint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"@expo/image-utils/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"@expo/local-build-cache-provider/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"@expo/metro/metro-runtime": ["metro-runtime@0.83.5", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-f+b3ue9AWTVlZe2Xrki6TAoFtKIqw30jwfk7GQ1rDUBQaE0ZQ+NkiMEtb9uwH7uAjJ87U7Tdx1Jg1OJqUfEVlA=="],
|
||||
|
||||
"@expo/metro/metro-source-map": ["metro-source-map@0.83.5", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.5", "nullthrows": "^1.1.1", "ob1": "0.83.5", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ=="],
|
||||
|
||||
"@expo/metro-config/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"@expo/metro-config/hermes-parser": ["hermes-parser@0.32.1", "", { "dependencies": { "hermes-estree": "0.32.1" } }, "sha512-175dz634X/W5AiwrpLdoMl/MOb17poLHyIqgyExlE8D9zQ1OPnoORnGMB5ltRKnpvQzBjMYvT2rN/sHeIfZW5Q=="],
|
||||
|
||||
"@expo/metro-config/postcss": ["postcss@8.4.49", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA=="],
|
||||
|
||||
"@expo/package-manager/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"@expo/package-manager/npm-package-arg": ["npm-package-arg@11.0.3", "", { "dependencies": { "hosted-git-info": "^7.0.0", "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" } }, "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw=="],
|
||||
|
||||
"@expo/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
|
||||
|
||||
"@expo/prebuild-config/xml2js": ["xml2js@0.6.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w=="],
|
||||
|
||||
"@expo/xcpretty/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"@expressive-code/plugin-shiki/shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="],
|
||||
|
||||
"@fastify/proxy-addr/ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="],
|
||||
@@ -5448,8 +6334,22 @@
|
||||
|
||||
"@hono/zod-validator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
|
||||
|
||||
"@jest/transform/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"@jest/transform/write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="],
|
||||
|
||||
"@jest/types/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"@jimp/core/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
|
||||
|
||||
"@jimp/js-png/pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
|
||||
|
||||
"@jimp/plugin-blit/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@jimp/plugin-circle/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
@@ -5486,12 +6386,16 @@
|
||||
|
||||
"@jsx-email/cli/esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="],
|
||||
|
||||
"@jsx-email/cli/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@jsx-email/cli/tailwindcss": ["tailwindcss@3.3.3", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.5.3", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.18.2", "lilconfig": "^2.1.0", "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", "postcss": "^8.4.23", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.1", "postcss-nested": "^6.0.1", "postcss-selector-parser": "^6.0.11", "resolve": "^1.22.2", "sucrase": "^3.32.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w=="],
|
||||
|
||||
"@jsx-email/cli/vite": ["vite@4.5.14", "", { "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", "rollup": "^3.27.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@types/node": ">= 14", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g=="],
|
||||
|
||||
"@jsx-email/doiuse-email/htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="],
|
||||
|
||||
"@jsx-email/tailwind/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="],
|
||||
|
||||
"@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||
@@ -5610,6 +6514,94 @@
|
||||
|
||||
"@protobuf-ts/plugin/typescript": ["typescript@3.9.10", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q=="],
|
||||
|
||||
"@radix-ui/react-arrow/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@radix-ui/react-collapsible/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@radix-ui/react-collection/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="],
|
||||
|
||||
"@radix-ui/react-dialog/react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="],
|
||||
|
||||
"@radix-ui/react-dismissable-layer/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@radix-ui/react-focus-scope/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@radix-ui/react-popover/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@radix-ui/react-popper/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@radix-ui/react-portal/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@radix-ui/react-presence/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@radix-ui/react-primitive/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@radix-ui/react-roving-focus/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="],
|
||||
|
||||
"@radix-ui/react-toggle/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@radix-ui/react-toggle-group/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@radix-ui/react-tooltip/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@radix-ui/react-use-effect-event/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
|
||||
|
||||
"@radix-ui/react-visually-hidden/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@react-native/babel-preset/babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.32.0", "", { "dependencies": { "hermes-parser": "0.32.0" } }, "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg=="],
|
||||
|
||||
"@react-native/babel-preset/react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
|
||||
|
||||
"@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
|
||||
"@react-native/codegen/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="],
|
||||
|
||||
"@react-native/codegen/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"@react-native/dev-middleware/open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="],
|
||||
|
||||
"@react-native/dev-middleware/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
|
||||
|
||||
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@shikijs/engine-javascript/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
|
||||
@@ -5662,6 +6654,8 @@
|
||||
|
||||
"@solidjs/start/vite-plugin-solid": ["vite-plugin-solid@2.11.12", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-FgjPcx2OwX9h6f28jli7A4bG7PP3te8uyakE5iqsmpq3Jqi1TWLgSroC9N6cMfGRU2zXsl4Q6ISvTr2VL0QHpA=="],
|
||||
|
||||
"@storybook/addon-docs/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"@tailwindcss/oxide/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
|
||||
@@ -5686,6 +6680,8 @@
|
||||
|
||||
"@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
|
||||
|
||||
"@testing-library/dom/pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
|
||||
|
||||
"@types/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
|
||||
|
||||
"@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
|
||||
@@ -5714,6 +6710,8 @@
|
||||
|
||||
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
|
||||
|
||||
"anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"app-builder-lib/@electron/get": ["@electron/get@3.1.0", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ=="],
|
||||
@@ -5744,10 +6742,18 @@
|
||||
|
||||
"aws-sdk/uuid": ["uuid@8.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw=="],
|
||||
|
||||
"babel-jest/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="],
|
||||
|
||||
"babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
|
||||
|
||||
"babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"babel-plugin-syntax-hermes-parser/hermes-parser": ["hermes-parser@0.32.1", "", { "dependencies": { "hermes-estree": "0.32.1" } }, "sha512-175dz634X/W5AiwrpLdoMl/MOb17poLHyIqgyExlE8D9zQ1OPnoORnGMB5ltRKnpvQzBjMYvT2rN/sHeIfZW5Q=="],
|
||||
|
||||
"better-opn/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="],
|
||||
|
||||
"bl/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
|
||||
|
||||
"body-parser/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
@@ -5768,10 +6774,20 @@
|
||||
|
||||
"c12/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
|
||||
|
||||
"chrome-launcher/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
||||
|
||||
"chromium-edge-launcher/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
||||
|
||||
"chromium-edge-launcher/mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
|
||||
|
||||
"clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="],
|
||||
|
||||
"compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
|
||||
"compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"compression/negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="],
|
||||
|
||||
"condense-newlines/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="],
|
||||
|
||||
"conf/dot-prop": ["dot-prop@9.0.0", "", { "dependencies": { "type-fest": "^4.18.2" } }, "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ=="],
|
||||
@@ -5780,6 +6796,10 @@
|
||||
|
||||
"config-chain/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
|
||||
|
||||
"connect/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"connect/finalhandler": ["finalhandler@1.1.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" } }, "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA=="],
|
||||
|
||||
"crc/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||
|
||||
"cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
@@ -5824,12 +6844,34 @@
|
||||
|
||||
"esbuild-plugin-copy/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"eslint/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
|
||||
|
||||
"eslint/glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"eslint-plugin-react-hooks/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
|
||||
"estree-util-to-js/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||
|
||||
"execa/get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="],
|
||||
|
||||
"execa/is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="],
|
||||
|
||||
"expo/react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
|
||||
|
||||
"expo-modules-autolinking/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
|
||||
|
||||
"expo-router/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],
|
||||
|
||||
"expo-router/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="],
|
||||
|
||||
"expo-updates/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"expo-updates/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||
|
||||
"express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
@@ -5838,6 +6880,10 @@
|
||||
|
||||
"express/qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="],
|
||||
|
||||
"fbjs/promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="],
|
||||
|
||||
"fbjs/ua-parser-js": ["ua-parser-js@1.0.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug=="],
|
||||
|
||||
"fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
||||
|
||||
"filelist/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="],
|
||||
@@ -5848,6 +6894,8 @@
|
||||
|
||||
"form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"framer-motion/react-dom": ["react-dom@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.0" }, "peerDependencies": { "react": "^18.2.0" } }, "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g=="],
|
||||
|
||||
"fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
|
||||
|
||||
"gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
||||
@@ -5862,6 +6910,8 @@
|
||||
|
||||
"happy-dom/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="],
|
||||
|
||||
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||
|
||||
"html-minifier-terser/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="],
|
||||
|
||||
"html-minifier-terser/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
@@ -5872,6 +6922,22 @@
|
||||
|
||||
"iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="],
|
||||
|
||||
"istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"jest-message-util/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"jest-util/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"jest-util/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
|
||||
|
||||
"jest-util/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"jest-validate/camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
|
||||
|
||||
"jest-validate/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||
|
||||
"js-beautify/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
|
||||
"js-beautify/nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="],
|
||||
@@ -5882,14 +6948,50 @@
|
||||
|
||||
"light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="],
|
||||
|
||||
"lighthouse-logger/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
||||
|
||||
"lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"log-symbols/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
"log-symbols/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
|
||||
|
||||
"md-to-react-email/marked": ["marked@7.0.4", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ=="],
|
||||
|
||||
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
|
||||
"metro/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||
|
||||
"metro/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"metro/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="],
|
||||
|
||||
"metro/hermes-parser": ["hermes-parser@0.33.3", "", { "dependencies": { "hermes-estree": "0.33.3" } }, "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA=="],
|
||||
|
||||
"metro/metro-runtime": ["metro-runtime@0.83.5", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-f+b3ue9AWTVlZe2Xrki6TAoFtKIqw30jwfk7GQ1rDUBQaE0ZQ+NkiMEtb9uwH7uAjJ87U7Tdx1Jg1OJqUfEVlA=="],
|
||||
|
||||
"metro/metro-source-map": ["metro-source-map@0.83.5", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.5", "nullthrows": "^1.1.1", "ob1": "0.83.5", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ=="],
|
||||
|
||||
"metro/serialize-error": ["serialize-error@2.1.0", "", {}, "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw=="],
|
||||
|
||||
"metro/source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
|
||||
|
||||
"metro/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
|
||||
|
||||
"metro/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"metro-babel-transformer/hermes-parser": ["hermes-parser@0.33.3", "", { "dependencies": { "hermes-estree": "0.33.3" } }, "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA=="],
|
||||
|
||||
"metro-config/metro-runtime": ["metro-runtime@0.83.5", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-f+b3ue9AWTVlZe2Xrki6TAoFtKIqw30jwfk7GQ1rDUBQaE0ZQ+NkiMEtb9uwH7uAjJ87U7Tdx1Jg1OJqUfEVlA=="],
|
||||
|
||||
"metro-source-map/metro-symbolicate": ["metro-symbolicate@0.83.6", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.6", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-4nvkmv9T7ozhprlPwk/+xm0SVPsxly5kYyMHdNaOlFemFz4df9BanvD46Ac6OISu/4Idinzfk2KVb++6OfzPAQ=="],
|
||||
|
||||
"metro-source-map/source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
|
||||
|
||||
"metro-symbolicate/metro-source-map": ["metro-source-map@0.83.5", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.5", "nullthrows": "^1.1.1", "ob1": "0.83.5", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ=="],
|
||||
|
||||
"metro-symbolicate/source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
|
||||
|
||||
"metro-transform-worker/metro-source-map": ["metro-source-map@0.83.5", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.5", "nullthrows": "^1.1.1", "ob1": "0.83.5", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"miniflare/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
|
||||
@@ -5902,6 +7004,12 @@
|
||||
|
||||
"minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
||||
|
||||
"mobile-voice/@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
||||
|
||||
"mobile-voice/react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
|
||||
|
||||
"mobile-voice/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"motion/framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="],
|
||||
|
||||
"mssql/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
|
||||
@@ -5944,15 +7052,13 @@
|
||||
|
||||
"openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
|
||||
|
||||
"ora/bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
|
||||
|
||||
"ora/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
"ora/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
|
||||
|
||||
"ora/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
|
||||
|
||||
"ora/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
"ora/strip-ansi": ["strip-ansi@5.2.0", "", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="],
|
||||
|
||||
"p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
"p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
|
||||
|
||||
@@ -5960,10 +7066,14 @@
|
||||
|
||||
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
||||
|
||||
"parse-png/pngjs": ["pngjs@3.4.0", "", {}, "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="],
|
||||
|
||||
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||
|
||||
"pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
|
||||
|
||||
"pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
|
||||
"pkg-up/find-up": ["find-up@3.0.0", "", { "dependencies": { "locate-path": "^3.0.0" } }, "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg=="],
|
||||
|
||||
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
|
||||
@@ -5978,21 +7088,53 @@
|
||||
|
||||
"postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="],
|
||||
|
||||
"pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
||||
|
||||
"pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
||||
|
||||
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
|
||||
|
||||
"proper-lockfile/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"qrcode/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
|
||||
|
||||
"raw-body/iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
|
||||
|
||||
"react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
|
||||
|
||||
"react-dom/react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
|
||||
|
||||
"react-native/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"react-native/babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.32.0", "", { "dependencies": { "hermes-parser": "0.32.0" } }, "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg=="],
|
||||
|
||||
"react-native/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
|
||||
|
||||
"react-native/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
|
||||
"react-native/react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
|
||||
|
||||
"react-native/react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
|
||||
|
||||
"react-native/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
|
||||
|
||||
"react-native/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"react-native-reanimated/react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.2.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q=="],
|
||||
|
||||
"react-native-reanimated/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
|
||||
"react-native-web/@react-native/normalize-colors": ["@react-native/normalize-colors@0.74.89", "", {}, "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg=="],
|
||||
|
||||
"react-native-web/memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
|
||||
|
||||
"react-native-worklets/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
|
||||
"readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
|
||||
|
||||
"readdir-glob/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="],
|
||||
|
||||
"restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||
"restore-cursor/onetime": ["onetime@2.0.1", "", { "dependencies": { "mimic-fn": "^1.0.0" } }, "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ=="],
|
||||
|
||||
"restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
@@ -6016,6 +7158,10 @@
|
||||
|
||||
"shiki/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
|
||||
|
||||
"simple-plist/bplist-parser": ["bplist-parser@0.3.1", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA=="],
|
||||
|
||||
"sitemap/arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
|
||||
|
||||
"sitemap/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
@@ -6028,6 +7174,10 @@
|
||||
|
||||
"sst/jose": ["jose@5.2.3", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="],
|
||||
|
||||
"stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
|
||||
|
||||
"stacktrace-parser/type-fest": ["type-fest@0.7.1", "", {}, "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg=="],
|
||||
|
||||
"storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
||||
|
||||
"storybook-solidjs-vite/vite-plugin-solid": ["vite-plugin-solid@2.11.12", "", { "dependencies": { "@babel/core": "^7.23.3", "@types/babel__core": "^7.20.4", "babel-preset-solid": "^1.8.4", "merge-anything": "^5.1.7", "solid-refresh": "^0.6.3", "vitefu": "^1.0.4" }, "peerDependencies": { "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", "solid-js": "^1.7.2", "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["@testing-library/jest-dom"] }, "sha512-FgjPcx2OwX9h6f28jli7A4bG7PP3te8uyakE5iqsmpq3Jqi1TWLgSroC9N6cMfGRU2zXsl4Q6ISvTr2VL0QHpA=="],
|
||||
@@ -6042,8 +7192,14 @@
|
||||
|
||||
"tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
|
||||
|
||||
"temp/rimraf": ["rimraf@2.6.3", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA=="],
|
||||
|
||||
"terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
|
||||
|
||||
"test-exclude/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
|
||||
"test-exclude/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
"tiny-async-pool/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="],
|
||||
|
||||
"token-types/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
@@ -6060,6 +7216,8 @@
|
||||
|
||||
"unifont/ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
|
||||
|
||||
"unused-filename/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
|
||||
"uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="],
|
||||
@@ -6090,6 +7248,8 @@
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"xcode/uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="],
|
||||
|
||||
"xml2js/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="],
|
||||
@@ -6104,6 +7264,8 @@
|
||||
|
||||
"zod-to-ts/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"zxing-wasm/type-fest": ["type-fest@5.6.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA=="],
|
||||
|
||||
"@actions/artifact/@actions/core/@actions/exec": ["@actions/exec@2.0.0", "", { "dependencies": { "@actions/io": "^2.0.0" } }, "sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw=="],
|
||||
|
||||
"@actions/core/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
|
||||
@@ -6352,6 +7514,18 @@
|
||||
|
||||
"@electron/rebuild/node-gyp/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
|
||||
|
||||
"@electron/rebuild/ora/bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
|
||||
|
||||
"@electron/rebuild/ora/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"@electron/rebuild/ora/cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
|
||||
|
||||
"@electron/rebuild/ora/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
|
||||
|
||||
"@electron/rebuild/ora/log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
|
||||
|
||||
"@electron/rebuild/ora/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@electron/rebuild/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"@electron/rebuild/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
@@ -6362,6 +7536,32 @@
|
||||
|
||||
"@electron/windows-sign/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
|
||||
|
||||
"@expo/cli/npm-package-arg/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="],
|
||||
|
||||
"@expo/cli/npm-package-arg/proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="],
|
||||
|
||||
"@expo/cli/npm-package-arg/validate-npm-package-name": ["validate-npm-package-name@5.0.1", "", {}, "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ=="],
|
||||
|
||||
"@expo/cli/wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"@expo/cli/wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@expo/config-plugins/xml2js/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"@expo/metro-config/hermes-parser/hermes-estree": ["hermes-estree@0.32.1", "", {}, "sha512-ne5hkuDxheNBAikDjqvCZCwihnz0vVu9YsBzAEO1puiyFR4F1+PAz/SiPHSsNTuOveCYGRMX8Xbx4LOubeC0Qg=="],
|
||||
|
||||
"@expo/metro/metro-source-map/ob1": ["ob1@0.83.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg=="],
|
||||
|
||||
"@expo/metro/metro-source-map/source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
|
||||
|
||||
"@expo/package-manager/npm-package-arg/hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="],
|
||||
|
||||
"@expo/package-manager/npm-package-arg/proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="],
|
||||
|
||||
"@expo/package-manager/npm-package-arg/validate-npm-package-name": ["validate-npm-package-name@5.0.1", "", {}, "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ=="],
|
||||
|
||||
"@expo/prebuild-config/xml2js/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"@expressive-code/plugin-shiki/shiki/@shikijs/core": ["@shikijs/core@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA=="],
|
||||
|
||||
"@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA=="],
|
||||
@@ -6376,6 +7576,14 @@
|
||||
|
||||
"@gitlab/opencode-gitlab-auth/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/find-up/path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
||||
|
||||
"@jest/transform/write-file-atomic/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"@jsx-email/cli/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
|
||||
|
||||
"@jsx-email/cli/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="],
|
||||
@@ -6422,6 +7630,10 @@
|
||||
|
||||
"@jsx-email/cli/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="],
|
||||
|
||||
"@jsx-email/cli/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@jsx-email/cli/tailwindcss/arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
|
||||
|
||||
"@jsx-email/cli/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"@jsx-email/cli/tailwindcss/glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
@@ -6436,6 +7648,8 @@
|
||||
|
||||
"@jsx-email/doiuse-email/htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"@jsx-email/tailwind/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@malept/flatpak-bundler/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
|
||||
|
||||
"@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||
@@ -6560,6 +7774,80 @@
|
||||
|
||||
"@pierre/diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="],
|
||||
|
||||
"@radix-ui/react-arrow/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@radix-ui/react-collapsible/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@radix-ui/react-collection/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-dismissable-layer/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-dismissable-layer/@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-focus-scope/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-id/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-portal/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-presence/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
|
||||
|
||||
"@radix-ui/react-dialog/@radix-ui/react-use-controllable-state/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
|
||||
|
||||
"@radix-ui/react-dismissable-layer/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@radix-ui/react-focus-scope/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@radix-ui/react-popover/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@radix-ui/react-popper/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@radix-ui/react-portal/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@radix-ui/react-presence/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@radix-ui/react-primitive/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@radix-ui/react-roving-focus/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-id/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-presence/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-presence/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-roving-focus/@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-roving-focus/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-roving-focus/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-use-controllable-state/@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
|
||||
|
||||
"@radix-ui/react-toggle-group/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@radix-ui/react-toggle/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@radix-ui/react-tooltip/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@radix-ui/react-visually-hidden/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@react-native/babel-preset/babel-plugin-syntax-hermes-parser/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="],
|
||||
|
||||
"@react-native/codegen/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
"@react-native/codegen/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="],
|
||||
|
||||
"@react-native/codegen/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"@react-native/codegen/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"@react-native/dev-middleware/open/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
|
||||
|
||||
"@react-native/dev-middleware/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
||||
|
||||
"@slack/web-api/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||
|
||||
"@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
|
||||
@@ -6578,8 +7866,16 @@
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/types": ["@shikijs/types@1.29.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="],
|
||||
|
||||
"@storybook/addon-docs/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@testing-library/dom/pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@testing-library/dom/pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
||||
|
||||
"@testing-library/dom/pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
||||
|
||||
"@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
|
||||
|
||||
"accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
@@ -6624,12 +7920,34 @@
|
||||
|
||||
"babel-plugin-module-resolver/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.32.1", "", {}, "sha512-ne5hkuDxheNBAikDjqvCZCwihnz0vVu9YsBzAEO1puiyFR4F1+PAz/SiPHSsNTuOveCYGRMX8Xbx4LOubeC0Qg=="],
|
||||
|
||||
"better-opn/open/define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="],
|
||||
|
||||
"better-opn/open/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
|
||||
|
||||
"better-opn/open/is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
||||
|
||||
"bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
||||
|
||||
"chrome-launcher/is-wsl/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
|
||||
|
||||
"chromium-edge-launcher/is-wsl/is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
|
||||
|
||||
"compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"connect/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"connect/finalhandler/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
|
||||
|
||||
"connect/finalhandler/on-finished": ["on-finished@2.3.0", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww=="],
|
||||
|
||||
"connect/finalhandler/statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="],
|
||||
|
||||
"crc/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
@@ -6650,6 +7968,10 @@
|
||||
|
||||
"esbuild-plugin-copy/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
"eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
|
||||
"expo-router/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
|
||||
|
||||
"express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"filelist/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="],
|
||||
@@ -6658,6 +7980,8 @@
|
||||
|
||||
"form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"framer-motion/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
||||
|
||||
"iconv-corefoundation/cli-truncate/slice-ansi": ["slice-ansi@3.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ=="],
|
||||
@@ -6678,6 +8002,30 @@
|
||||
|
||||
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
|
||||
"lighthouse-logger/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"log-symbols/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
|
||||
|
||||
"log-symbols/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
|
||||
|
||||
"log-symbols/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
|
||||
|
||||
"metro-babel-transformer/hermes-parser/hermes-estree": ["hermes-estree@0.33.3", "", {}, "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg=="],
|
||||
|
||||
"metro-symbolicate/metro-source-map/ob1": ["ob1@0.83.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg=="],
|
||||
|
||||
"metro-transform-worker/metro-source-map/ob1": ["ob1@0.83.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg=="],
|
||||
|
||||
"metro-transform-worker/metro-source-map/source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
|
||||
|
||||
"metro/hermes-parser/hermes-estree": ["hermes-estree@0.33.3", "", {}, "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg=="],
|
||||
|
||||
"metro/metro-source-map/ob1": ["ob1@0.83.5", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg=="],
|
||||
|
||||
"metro/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"metro/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"motion/framer-motion/motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="],
|
||||
|
||||
"motion/framer-motion/motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="],
|
||||
@@ -6700,21 +8048,47 @@
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
|
||||
|
||||
"ora/bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||
"ora/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
|
||||
|
||||
"ora/bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
"ora/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
|
||||
|
||||
"ora/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
"ora/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
|
||||
|
||||
"ora/strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="],
|
||||
|
||||
"p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"parse-bmfont-xml/xml2js/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
|
||||
"pkg-dir/find-up/path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"pkg-up/find-up/locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
|
||||
|
||||
"qrcode/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],
|
||||
|
||||
"qrcode/yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
|
||||
"qrcode/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"qrcode/yargs/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="],
|
||||
|
||||
"qrcode/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
|
||||
|
||||
"react-native/babel-plugin-syntax-hermes-parser/hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="],
|
||||
|
||||
"react-native/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
"react-native/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"react-native/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"readdir-glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="],
|
||||
|
||||
"restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
||||
"restore-cursor/onetime/mimic-fn": ["mimic-fn@1.2.0", "", {}, "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="],
|
||||
|
||||
"rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
@@ -6724,6 +8098,12 @@
|
||||
|
||||
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"temp/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
|
||||
|
||||
"test-exclude/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="],
|
||||
|
||||
"tw-to-css/tailwindcss/arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
|
||||
|
||||
"tw-to-css/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
||||
"tw-to-css/tailwindcss/glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
@@ -6878,6 +8258,14 @@
|
||||
|
||||
"@electron/rebuild/node-gyp/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
|
||||
|
||||
"@electron/rebuild/ora/bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||
|
||||
"@electron/rebuild/ora/bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"@electron/rebuild/ora/cli-cursor/restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
|
||||
|
||||
"@electron/rebuild/ora/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@electron/rebuild/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@electron/rebuild/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
@@ -6888,6 +8276,18 @@
|
||||
|
||||
"@electron/universal/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"@expo/cli/npm-package-arg/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"@expo/cli/wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"@expo/cli/wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@expo/package-manager/npm-package-arg/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/js-yaml/argparse/sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
|
||||
|
||||
"@jsx-email/cli/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"@jsx-email/cli/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
@@ -6960,6 +8360,22 @@
|
||||
|
||||
"@opencode-ai/desktop/@actions/artifact/@actions/http-client/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
|
||||
|
||||
"@radix-ui/react-tabs/@radix-ui/react-roving-focus/@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
||||
|
||||
"@react-native/babel-preset/babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="],
|
||||
|
||||
"@react-native/codegen/glob/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="],
|
||||
|
||||
"@react-native/codegen/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@react-native/codegen/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"@react-native/codegen/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"@react-native/codegen/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@slack/web-api/form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="],
|
||||
@@ -7020,6 +8436,18 @@
|
||||
|
||||
"js-beautify/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"log-symbols/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
|
||||
|
||||
"log-symbols/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
|
||||
|
||||
"metro/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"metro/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"metro/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"metro/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
|
||||
@@ -7042,16 +8470,50 @@
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
|
||||
|
||||
"ora/bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
"ora/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
|
||||
|
||||
"ora/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
|
||||
|
||||
"qrcode/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"qrcode/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
|
||||
|
||||
"qrcode/yargs/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
|
||||
"qrcode/yargs/find-up/path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"qrcode/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"qrcode/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"qrcode/yargs/yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
|
||||
|
||||
"react-native/babel-plugin-syntax-hermes-parser/hermes-parser/hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="],
|
||||
|
||||
"react-native/glob/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="],
|
||||
|
||||
"react-native/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"react-native/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"react-native/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"react-native/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"readdir-glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="],
|
||||
|
||||
"temp/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
"test-exclude/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"tw-to-css/tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
@@ -7104,12 +8566,26 @@
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch/minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="],
|
||||
|
||||
"@electron/rebuild/ora/bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"@electron/rebuild/ora/cli-cursor/restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||
|
||||
"@electron/rebuild/ora/cli-cursor/restore-cursor/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"@electron/rebuild/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@electron/rebuild/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
|
||||
"@jsx-email/cli/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"@react-native/codegen/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"@react-native/codegen/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@react-native/codegen/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="],
|
||||
|
||||
"@solidjs/start/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="],
|
||||
@@ -7134,12 +8610,32 @@
|
||||
|
||||
"js-beautify/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"log-symbols/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
|
||||
|
||||
"metro/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"metro/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"opencontrol/@modelcontextprotocol/sdk/express/type-is/media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||
|
||||
"ora/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
|
||||
|
||||
"pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
|
||||
"pkg-up/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
|
||||
"qrcode/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"qrcode/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
|
||||
"qrcode/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"react-native/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"temp/rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="],
|
||||
|
||||
"tw-to-css/tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"@aws-sdk/client-sts/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers/@aws-sdk/nested-clients/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
||||
@@ -7154,6 +8650,8 @@
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch/minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
||||
|
||||
"@electron/rebuild/ora/cli-cursor/restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
||||
|
||||
"archiver-utils/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"archiver-utils/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
@@ -7162,6 +8660,10 @@
|
||||
|
||||
"js-beautify/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"qrcode/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||
|
||||
"temp/rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="],
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
PORT=8787
|
||||
|
||||
DATABASE_HOST=
|
||||
DATABASE_USERNAME=
|
||||
DATABASE_PASSWORD=
|
||||
DATABASE_NAME=main
|
||||
|
||||
APNS_TEAM_ID=
|
||||
APNS_KEY_ID=
|
||||
APNS_PRIVATE_KEY=
|
||||
APNS_DEFAULT_BUNDLE_ID=com.anomalyco.mobilevoice
|
||||
@@ -0,0 +1,106 @@
|
||||
# apn-relay Agent Guide
|
||||
|
||||
This file defines package-specific guidance for agents working in `packages/apn-relay`.
|
||||
|
||||
## Scope And Precedence
|
||||
|
||||
- Follow root `AGENTS.md` first.
|
||||
- This file provides stricter package-level conventions for relay service work.
|
||||
- If future local guides are added, closest guide wins.
|
||||
|
||||
## Project Overview
|
||||
|
||||
- Minimal APNs relay service (Hono + Bun + PlanetScale via Drizzle).
|
||||
- Core routes:
|
||||
- `GET /health`
|
||||
- `GET /`
|
||||
- `POST /v1/device/register`
|
||||
- `POST /v1/device/unregister`
|
||||
- `POST /v1/event`
|
||||
|
||||
## Commands
|
||||
|
||||
Run all commands from `packages/apn-relay`.
|
||||
|
||||
- Install deps: `bun install`
|
||||
- Start relay locally: `bun run dev`
|
||||
- Typecheck: `bun run typecheck`
|
||||
- DB connectivity check: `bun run db:check`
|
||||
|
||||
## Build / Test Expectations
|
||||
|
||||
- There is no dedicated package test script currently.
|
||||
- Required validation for behavior changes:
|
||||
- `bun run typecheck`
|
||||
- `bun run db:check` when DB/env changes are involved
|
||||
- manual endpoint verification against `/health`, `/v1/device/register`, `/v1/event`
|
||||
|
||||
## Single-Test Guidance
|
||||
|
||||
- No single-test command exists for this package today.
|
||||
- For focused checks, run endpoint-level manual tests against a local dev server.
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
### Formatting / Structure
|
||||
|
||||
- Keep handlers compact and explicit.
|
||||
- Prefer small local helpers for repeated route logic.
|
||||
- Avoid broad refactors when a targeted fix is enough.
|
||||
|
||||
### Types / Validation
|
||||
|
||||
- Validate request bodies with `zod` at route boundaries.
|
||||
- Keep payload and DB row shapes explicit and close to usage.
|
||||
- Avoid `any`; narrow unknown input immediately after parsing.
|
||||
|
||||
### Naming
|
||||
|
||||
- Follow existing concise naming in this package (`reg`, `unreg`, `evt`, `row`, `key`).
|
||||
- For DB columns, keep snake_case alignment with schema.
|
||||
|
||||
### Error Handling
|
||||
|
||||
- Return clear JSON errors for invalid input.
|
||||
- Keep handler failures observable via `app.onError` and structured logs.
|
||||
- Do not leak secrets in responses or logs.
|
||||
|
||||
### Logging
|
||||
|
||||
- Log delivery lifecycle at key checkpoints:
|
||||
- registration/unregistration attempts
|
||||
- event fanout start/end
|
||||
- APNs send failures and retries
|
||||
- Mask sensitive values; prefer token suffixes and metadata.
|
||||
|
||||
### APNs Environment Rules
|
||||
|
||||
- Keep APNs env explicit per registration (`sandbox` / `production`).
|
||||
- For `BadEnvironmentKeyInToken`, retry once with flipped env and persist correction.
|
||||
- Avoid infinite retry loops; one retry max per delivery attempt.
|
||||
|
||||
## Database Conventions
|
||||
|
||||
- Schema is in `src/schema.sql.ts`.
|
||||
- Keep table/column names snake_case.
|
||||
- Maintain index naming consistency with existing schema.
|
||||
- For upserts, update only fields required by current behavior.
|
||||
|
||||
## API Behavior Expectations
|
||||
|
||||
- `register`/`unregister` must be idempotent.
|
||||
- `event` should return success envelope even when no devices are registered.
|
||||
- Delivery logs should capture per-attempt result and error payload.
|
||||
|
||||
## Operational Notes
|
||||
|
||||
- Ensure `APNS_PRIVATE_KEY` supports escaped newline format (`\n`) and raw multiline.
|
||||
- Validate that `APNS_DEFAULT_BUNDLE_ID` matches mobile app bundle identifier.
|
||||
- Avoid coupling route behavior to deployment platform specifics.
|
||||
|
||||
## Before Finishing
|
||||
|
||||
- Run `bun run typecheck`.
|
||||
- If DB/env behavior changed, run `bun run db:check`.
|
||||
- Manually exercise affected endpoints.
|
||||
- Confirm logs are useful and secret-safe.
|
||||
@@ -0,0 +1,14 @@
|
||||
FROM oven/bun:1.3.11-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json ./
|
||||
COPY tsconfig.json ./
|
||||
COPY drizzle.config.ts ./
|
||||
RUN bun install --production
|
||||
|
||||
COPY src ./src
|
||||
|
||||
EXPOSE 8787
|
||||
|
||||
CMD ["bun", "run", "src/index.ts"]
|
||||
@@ -0,0 +1,46 @@
|
||||
# APN Relay
|
||||
|
||||
Minimal APNs relay for OpenCode mobile background notifications.
|
||||
|
||||
## What it does
|
||||
|
||||
- Registers iOS device tokens for a shared secret.
|
||||
- Receives OpenCode event posts (`complete`, `permission`, `error`).
|
||||
- Sends APNs notifications to mapped devices.
|
||||
- Stores delivery rows in PlanetScale.
|
||||
|
||||
## Routes
|
||||
|
||||
- `GET /health`
|
||||
- `GET /` (simple dashboard)
|
||||
- `POST /v1/device/register`
|
||||
- `POST /v1/device/unregister`
|
||||
- `POST /v1/event`
|
||||
|
||||
## Environment
|
||||
|
||||
Use `.env.example` as a starting point.
|
||||
|
||||
- `DATABASE_HOST`
|
||||
- `DATABASE_USERNAME`
|
||||
- `DATABASE_PASSWORD`
|
||||
- `APNS_TEAM_ID`
|
||||
- `APNS_KEY_ID`
|
||||
- `APNS_PRIVATE_KEY`
|
||||
- `APNS_DEFAULT_BUNDLE_ID`
|
||||
|
||||
## Run locally
|
||||
|
||||
```bash
|
||||
bun install
|
||||
bun run src/index.ts
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
Build from this directory:
|
||||
|
||||
```bash
|
||||
docker build -t apn-relay .
|
||||
docker run --rm -p 8787:8787 --env-file .env apn-relay
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from "drizzle-kit"
|
||||
|
||||
export default defineConfig({
|
||||
out: "./migration",
|
||||
strict: true,
|
||||
schema: ["./src/**/*.sql.ts"],
|
||||
dialect: "mysql",
|
||||
dbCredentials: {
|
||||
host: process.env.DATABASE_HOST ?? "",
|
||||
user: process.env.DATABASE_USERNAME ?? "",
|
||||
password: process.env.DATABASE_PASSWORD ?? "",
|
||||
database: process.env.DATABASE_NAME ?? "main",
|
||||
ssl: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/apn-relay",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "bun run src/index.ts",
|
||||
"db:check": "bun run --env-file .env src/check.ts",
|
||||
"typecheck": "tsgo --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@planetscale/database": "1.19.0",
|
||||
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
||||
"hono": "4.10.7",
|
||||
"jose": "6.0.11",
|
||||
"zod": "4.1.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@types/bun": "1.3.11",
|
||||
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
||||
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
||||
"typescript": "5.8.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
import { connect } from "node:http2"
|
||||
import { SignJWT, importPKCS8 } from "jose"
|
||||
import { env } from "./env"
|
||||
|
||||
export type PushEnv = "sandbox" | "production"
|
||||
|
||||
type PushInput = {
|
||||
token: string
|
||||
bundle: string
|
||||
env: PushEnv
|
||||
title: string
|
||||
body: string
|
||||
data: Record<string, unknown>
|
||||
}
|
||||
|
||||
type PushResult = {
|
||||
ok: boolean
|
||||
code: number
|
||||
error?: string
|
||||
}
|
||||
|
||||
function tokenSuffix(input: string) {
|
||||
return input.length > 8 ? input.slice(-8) : input
|
||||
}
|
||||
|
||||
let jwt = ""
|
||||
let exp = 0
|
||||
let pk: Awaited<ReturnType<typeof importPKCS8>> | undefined
|
||||
|
||||
function host(input: PushEnv) {
|
||||
if (input === "sandbox") return "api.sandbox.push.apple.com"
|
||||
return "api.push.apple.com"
|
||||
}
|
||||
|
||||
function key() {
|
||||
if (env.APNS_PRIVATE_KEY.includes("\\n")) return env.APNS_PRIVATE_KEY.replace(/\\n/g, "\n")
|
||||
return env.APNS_PRIVATE_KEY
|
||||
}
|
||||
|
||||
async function sign() {
|
||||
if (!pk) pk = await importPKCS8(key(), "ES256")
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
if (jwt && now < exp) return jwt
|
||||
jwt = await new SignJWT({})
|
||||
.setProtectedHeader({ alg: "ES256", kid: env.APNS_KEY_ID })
|
||||
.setIssuer(env.APNS_TEAM_ID)
|
||||
.setIssuedAt(now)
|
||||
.sign(pk)
|
||||
exp = now + 50 * 60
|
||||
return jwt
|
||||
}
|
||||
|
||||
function post(input: {
|
||||
host: string
|
||||
token: string
|
||||
auth: string
|
||||
bundle: string
|
||||
payload: string
|
||||
}): Promise<{ code: number; body: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const cli = connect(`https://${input.host}`)
|
||||
let done = false
|
||||
let code = 0
|
||||
let body = ""
|
||||
|
||||
const stop = (fn: () => void) => {
|
||||
if (done) return
|
||||
done = true
|
||||
fn()
|
||||
}
|
||||
|
||||
cli.on("error", (err) => {
|
||||
stop(() => reject(err))
|
||||
cli.close()
|
||||
})
|
||||
|
||||
const req = cli.request({
|
||||
":method": "POST",
|
||||
":path": `/3/device/${input.token}`,
|
||||
authorization: `bearer ${input.auth}`,
|
||||
"apns-topic": input.bundle,
|
||||
"apns-push-type": "alert",
|
||||
"apns-priority": "10",
|
||||
"content-type": "application/json",
|
||||
})
|
||||
|
||||
req.setEncoding("utf8")
|
||||
req.on("response", (headers) => {
|
||||
code = Number(headers[":status"] ?? 0)
|
||||
})
|
||||
req.on("data", (chunk) => {
|
||||
body += chunk
|
||||
})
|
||||
req.on("end", () => {
|
||||
stop(() => resolve({ code, body }))
|
||||
cli.close()
|
||||
})
|
||||
req.on("error", (err) => {
|
||||
stop(() => reject(err))
|
||||
cli.close()
|
||||
})
|
||||
req.end(input.payload)
|
||||
})
|
||||
}
|
||||
|
||||
export async function send(input: PushInput): Promise<PushResult> {
|
||||
const apnsHost = host(input.env)
|
||||
const suffix = tokenSuffix(input.token)
|
||||
|
||||
console.log("[ APN RELAY ] push:start", {
|
||||
env: input.env,
|
||||
host: apnsHost,
|
||||
bundle: input.bundle,
|
||||
tokenSuffix: suffix,
|
||||
})
|
||||
|
||||
const auth = await sign().catch((err) => {
|
||||
return `error:${String(err)}`
|
||||
})
|
||||
if (auth.startsWith("error:")) {
|
||||
console.log("[ APN RELAY ] push:auth-failed", {
|
||||
env: input.env,
|
||||
host: apnsHost,
|
||||
bundle: input.bundle,
|
||||
tokenSuffix: suffix,
|
||||
error: auth,
|
||||
})
|
||||
return {
|
||||
ok: false,
|
||||
code: 0,
|
||||
error: auth,
|
||||
}
|
||||
}
|
||||
|
||||
const payload = JSON.stringify({
|
||||
aps: {
|
||||
alert: {
|
||||
title: input.title,
|
||||
body: input.body,
|
||||
},
|
||||
sound: "alert.wav",
|
||||
},
|
||||
...input.data,
|
||||
})
|
||||
|
||||
const out = await post({
|
||||
host: apnsHost,
|
||||
token: input.token,
|
||||
auth,
|
||||
bundle: input.bundle,
|
||||
payload,
|
||||
}).catch((err) => ({
|
||||
code: 0,
|
||||
body: String(err),
|
||||
}))
|
||||
|
||||
if (out.code === 200) {
|
||||
console.log("[ APN RELAY ] push:sent", {
|
||||
env: input.env,
|
||||
host: apnsHost,
|
||||
bundle: input.bundle,
|
||||
tokenSuffix: suffix,
|
||||
code: out.code,
|
||||
})
|
||||
return {
|
||||
ok: true,
|
||||
code: 200,
|
||||
}
|
||||
}
|
||||
|
||||
console.log("[ APN RELAY ] push:failed", {
|
||||
env: input.env,
|
||||
host: apnsHost,
|
||||
bundle: input.bundle,
|
||||
tokenSuffix: suffix,
|
||||
code: out.code,
|
||||
error: out.body,
|
||||
})
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
code: out.code,
|
||||
error: out.body,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { sql } from "drizzle-orm"
|
||||
import { db } from "./db"
|
||||
import { env } from "./env"
|
||||
import { delivery_log, device_registration } from "./schema.sql"
|
||||
import { setup } from "./setup"
|
||||
|
||||
async function run() {
|
||||
console.log(`[apn-relay] DB host: ${env.DATABASE_HOST}`)
|
||||
|
||||
await db.execute(sql`SELECT 1`)
|
||||
console.log("[apn-relay] DB connection OK")
|
||||
|
||||
await setup()
|
||||
console.log("[apn-relay] Setup migration OK")
|
||||
|
||||
const [a] = await db.select({ value: sql<number>`count(*)` }).from(device_registration)
|
||||
const [b] = await db.select({ value: sql<number>`count(*)` }).from(delivery_log)
|
||||
|
||||
console.log(`[apn-relay] device_registration rows: ${Number(a?.value ?? 0)}`)
|
||||
console.log(`[apn-relay] delivery_log rows: ${Number(b?.value ?? 0)}`)
|
||||
console.log("[apn-relay] DB check passed")
|
||||
}
|
||||
|
||||
run().catch((err) => {
|
||||
console.error("[apn-relay] DB check failed")
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Client } from "@planetscale/database"
|
||||
import { drizzle } from "drizzle-orm/planetscale-serverless"
|
||||
import { env } from "./env"
|
||||
|
||||
const client = new Client({
|
||||
host: env.DATABASE_HOST,
|
||||
username: env.DATABASE_USERNAME,
|
||||
password: env.DATABASE_PASSWORD,
|
||||
})
|
||||
|
||||
export const db = drizzle({ client })
|
||||
@@ -0,0 +1,47 @@
|
||||
import { z } from "zod"
|
||||
|
||||
const bad = new Set(["undefined", "null"])
|
||||
const txt = z
|
||||
.string()
|
||||
.transform((input) => input.trim())
|
||||
.refine((input) => input.length > 0 && !bad.has(input.toLowerCase()))
|
||||
|
||||
const schema = z.object({
|
||||
PORT: z.coerce.number().int().positive().default(8787),
|
||||
DATABASE_HOST: txt,
|
||||
DATABASE_USERNAME: txt,
|
||||
DATABASE_PASSWORD: txt,
|
||||
APNS_TEAM_ID: txt,
|
||||
APNS_KEY_ID: txt,
|
||||
APNS_PRIVATE_KEY: txt,
|
||||
APNS_DEFAULT_BUNDLE_ID: txt,
|
||||
})
|
||||
|
||||
const req = [
|
||||
"DATABASE_HOST",
|
||||
"DATABASE_USERNAME",
|
||||
"DATABASE_PASSWORD",
|
||||
"APNS_TEAM_ID",
|
||||
"APNS_KEY_ID",
|
||||
"APNS_PRIVATE_KEY",
|
||||
"APNS_DEFAULT_BUNDLE_ID",
|
||||
] as const
|
||||
|
||||
const out = schema.safeParse(process.env)
|
||||
|
||||
if (!out.success) {
|
||||
const miss = req.filter((key) => !process.env[key]?.trim())
|
||||
const bad = out.error.issues
|
||||
.map((item) => item.path[0])
|
||||
.filter((key): key is string => typeof key === "string")
|
||||
.filter((key) => !miss.includes(key as (typeof req)[number]))
|
||||
|
||||
console.error("[apn-relay] Invalid startup configuration")
|
||||
if (miss.length) console.error(`[apn-relay] Missing required env vars: ${miss.join(", ")}`)
|
||||
if (bad.length) console.error(`[apn-relay] Invalid env vars: ${Array.from(new Set(bad)).join(", ")}`)
|
||||
console.error("[apn-relay] Check .env.example and restart")
|
||||
|
||||
throw new Error("Startup configuration invalid")
|
||||
}
|
||||
|
||||
export const env = out.data
|
||||
@@ -0,0 +1,5 @@
|
||||
import { createHash } from "node:crypto"
|
||||
|
||||
export function hash(input: string) {
|
||||
return createHash("sha256").update(input).digest("hex")
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
import { randomUUID } from "node:crypto"
|
||||
import { and, desc, eq, sql } from "drizzle-orm"
|
||||
import { Hono } from "hono"
|
||||
import { z } from "zod"
|
||||
import { send } from "./apns"
|
||||
import { db } from "./db"
|
||||
import { env } from "./env"
|
||||
import { hash } from "./hash"
|
||||
import { delivery_log, device_registration } from "./schema.sql"
|
||||
import { setup } from "./setup"
|
||||
|
||||
function bad(input?: string) {
|
||||
if (!input) return false
|
||||
return input.includes("BadEnvironmentKeyInToken")
|
||||
}
|
||||
|
||||
function flip(input: "sandbox" | "production") {
|
||||
if (input === "sandbox") return "production"
|
||||
return "sandbox"
|
||||
}
|
||||
|
||||
function tail(input: string) {
|
||||
return input.slice(-8)
|
||||
}
|
||||
|
||||
function esc(input: unknown) {
|
||||
return String(input ?? "")
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """)
|
||||
.replaceAll("'", "'")
|
||||
}
|
||||
|
||||
function fmt(input: number) {
|
||||
return new Date(input).toISOString()
|
||||
}
|
||||
|
||||
const reg = z.object({
|
||||
secret: z.string().min(1),
|
||||
deviceToken: z.string().min(1),
|
||||
bundleId: z.string().min(1).optional(),
|
||||
apnsEnv: z.enum(["sandbox", "production"]).default("production"),
|
||||
})
|
||||
|
||||
const unreg = z.object({
|
||||
secret: z.string().min(1),
|
||||
deviceToken: z.string().min(1),
|
||||
})
|
||||
|
||||
const evt = z.object({
|
||||
secret: z.string().min(1),
|
||||
serverID: z.string().min(1).optional(),
|
||||
eventType: z.enum(["complete", "permission", "error"]),
|
||||
sessionID: z.string().min(1),
|
||||
title: z.string().min(1).optional(),
|
||||
body: z.string().min(1).optional(),
|
||||
})
|
||||
|
||||
function title(input: z.infer<typeof evt>["eventType"]) {
|
||||
if (input === "complete") return "Session complete"
|
||||
if (input === "permission") return "Action needed"
|
||||
return "Session error"
|
||||
}
|
||||
|
||||
function body(input: z.infer<typeof evt>["eventType"]) {
|
||||
if (input === "complete") return "OpenCode finished your session."
|
||||
if (input === "permission") return "OpenCode needs your permission decision."
|
||||
return "OpenCode reported an error for your session."
|
||||
}
|
||||
|
||||
const app = new Hono()
|
||||
|
||||
app.onError((err, c) => {
|
||||
return c.json(
|
||||
{
|
||||
ok: false,
|
||||
error: err.message,
|
||||
},
|
||||
500,
|
||||
)
|
||||
})
|
||||
|
||||
app.notFound((c) => {
|
||||
return c.json(
|
||||
{
|
||||
ok: false,
|
||||
error: "Not found",
|
||||
},
|
||||
404,
|
||||
)
|
||||
})
|
||||
|
||||
app.get("/health", async (c) => {
|
||||
const [a] = await db.select({ value: sql<number>`count(*)` }).from(device_registration)
|
||||
const [b] = await db.select({ value: sql<number>`count(*)` }).from(delivery_log)
|
||||
return c.json({
|
||||
ok: true,
|
||||
devices: Number(a?.value ?? 0),
|
||||
deliveries: Number(b?.value ?? 0),
|
||||
})
|
||||
})
|
||||
|
||||
app.get("/", async (c) => {
|
||||
const [a] = await db.select({ value: sql<number>`count(*)` }).from(device_registration)
|
||||
const [b] = await db.select({ value: sql<number>`count(*)` }).from(delivery_log)
|
||||
const devices = await db.select().from(device_registration).orderBy(desc(device_registration.updated_at)).limit(100)
|
||||
const byBundle = await db
|
||||
.select({
|
||||
bundle: device_registration.bundle_id,
|
||||
env: device_registration.apns_env,
|
||||
value: sql<number>`count(*)`,
|
||||
})
|
||||
.from(device_registration)
|
||||
.groupBy(device_registration.bundle_id, device_registration.apns_env)
|
||||
.orderBy(desc(sql<number>`count(*)`))
|
||||
const rows = await db.select().from(delivery_log).orderBy(desc(delivery_log.created_at)).limit(20)
|
||||
|
||||
const html = `<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>APN Relay</title>
|
||||
<style>
|
||||
body { font-family: ui-sans-serif, system-ui, sans-serif; margin: 24px; color: #111827; }
|
||||
h1 { margin: 0 0 12px 0; }
|
||||
h2 { margin: 22px 0 10px 0; font-size: 16px; }
|
||||
.stats { display: flex; gap: 16px; margin: 0 0 18px 0; }
|
||||
.card { border: 1px solid #e5e7eb; border-radius: 8px; padding: 10px 12px; min-width: 160px; }
|
||||
.muted { color: #6b7280; font-size: 12px; }
|
||||
.small { font-size: 11px; color: #6b7280; }
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
th, td { border: 1px solid #e5e7eb; text-align: left; padding: 8px; font-size: 12px; }
|
||||
th { background: #f9fafb; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>APN Relay</h1>
|
||||
<p class="muted">MVP dashboard</p>
|
||||
<div class="stats">
|
||||
<div class="card">
|
||||
<div class="muted">Registered devices</div>
|
||||
<div>${Number(a?.value ?? 0)}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="muted">Delivery log rows</div>
|
||||
<div>${Number(b?.value ?? 0)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Registered devices</h2>
|
||||
<p class="small">Most recent 100 registrations. Token values are masked to suffix only.</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>updated</th>
|
||||
<th>created</th>
|
||||
<th>token suffix</th>
|
||||
<th>env</th>
|
||||
<th>bundle</th>
|
||||
<th>secret hash</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${
|
||||
devices.length
|
||||
? devices
|
||||
.map(
|
||||
(row) => `<tr>
|
||||
<td>${esc(fmt(row.updated_at))}</td>
|
||||
<td>${esc(fmt(row.created_at))}</td>
|
||||
<td>${esc(tail(row.device_token))}</td>
|
||||
<td>${esc(row.apns_env)}</td>
|
||||
<td>${esc(row.bundle_id)}</td>
|
||||
<td>${esc(`${row.secret_hash.slice(0, 12)}…`)}</td>
|
||||
</tr>`,
|
||||
)
|
||||
.join("")
|
||||
: `<tr><td colspan="6" class="muted">No devices registered.</td></tr>`
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<h2>Bundle breakdown</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>bundle</th>
|
||||
<th>env</th>
|
||||
<th>count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${
|
||||
byBundle.length
|
||||
? byBundle
|
||||
.map(
|
||||
(row) => `<tr>
|
||||
<td>${esc(row.bundle)}</td>
|
||||
<td>${esc(row.env)}</td>
|
||||
<td>${esc(Number(row.value ?? 0))}</td>
|
||||
</tr>`,
|
||||
)
|
||||
.join("")
|
||||
: `<tr><td colspan="3" class="muted">No device data.</td></tr>`
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<h2>Recent deliveries</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>time</th>
|
||||
<th>event</th>
|
||||
<th>session</th>
|
||||
<th>status</th>
|
||||
<th>error</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${rows
|
||||
.map(
|
||||
(row) => `<tr>
|
||||
<td>${esc(fmt(row.created_at))}</td>
|
||||
<td>${esc(row.event_type)}</td>
|
||||
<td>${esc(row.session_id)}</td>
|
||||
<td>${esc(row.status)}</td>
|
||||
<td>${esc(row.error ?? "")}</td>
|
||||
</tr>`,
|
||||
)
|
||||
.join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
return c.html(html)
|
||||
})
|
||||
|
||||
app.post("/v1/device/register", async (c) => {
|
||||
const raw = await c.req.json().catch(() => undefined)
|
||||
const check = reg.safeParse(raw)
|
||||
if (!check.success) {
|
||||
return c.json(
|
||||
{
|
||||
ok: false,
|
||||
error: "Invalid request body",
|
||||
},
|
||||
400,
|
||||
)
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
const key = hash(check.data.secret)
|
||||
const row = {
|
||||
id: randomUUID(),
|
||||
secret_hash: key,
|
||||
device_token: check.data.deviceToken,
|
||||
bundle_id: check.data.bundleId ?? env.APNS_DEFAULT_BUNDLE_ID,
|
||||
apns_env: check.data.apnsEnv,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
}
|
||||
|
||||
console.log("[relay] register", {
|
||||
token: tail(row.device_token),
|
||||
env: row.apns_env,
|
||||
bundle: row.bundle_id,
|
||||
secretHash: `${key.slice(0, 12)}...`,
|
||||
})
|
||||
|
||||
await db
|
||||
.insert(device_registration)
|
||||
.values(row)
|
||||
.onDuplicateKeyUpdate({
|
||||
set: {
|
||||
bundle_id: row.bundle_id,
|
||||
apns_env: row.apns_env,
|
||||
updated_at: now,
|
||||
},
|
||||
})
|
||||
|
||||
return c.json({ ok: true })
|
||||
})
|
||||
|
||||
app.post("/v1/device/unregister", async (c) => {
|
||||
const raw = await c.req.json().catch(() => undefined)
|
||||
const check = unreg.safeParse(raw)
|
||||
if (!check.success) {
|
||||
return c.json(
|
||||
{
|
||||
ok: false,
|
||||
error: "Invalid request body",
|
||||
},
|
||||
400,
|
||||
)
|
||||
}
|
||||
|
||||
const key = hash(check.data.secret)
|
||||
|
||||
console.log("[relay] unregister", {
|
||||
token: tail(check.data.deviceToken),
|
||||
secretHash: `${key.slice(0, 12)}...`,
|
||||
})
|
||||
|
||||
await db
|
||||
.delete(device_registration)
|
||||
.where(and(eq(device_registration.secret_hash, key), eq(device_registration.device_token, check.data.deviceToken)))
|
||||
|
||||
return c.json({ ok: true })
|
||||
})
|
||||
|
||||
app.post("/v1/event", async (c) => {
|
||||
const raw = await c.req.json().catch(() => undefined)
|
||||
const check = evt.safeParse(raw)
|
||||
if (!check.success) {
|
||||
return c.json(
|
||||
{
|
||||
ok: false,
|
||||
error: "Invalid request body",
|
||||
},
|
||||
400,
|
||||
)
|
||||
}
|
||||
|
||||
const key = hash(check.data.secret)
|
||||
const list = await db.select().from(device_registration).where(eq(device_registration.secret_hash, key))
|
||||
console.log("[relay] event", {
|
||||
type: check.data.eventType,
|
||||
serverID: check.data.serverID,
|
||||
session: check.data.sessionID,
|
||||
secretHash: `${key.slice(0, 12)}...`,
|
||||
devices: list.length,
|
||||
})
|
||||
if (!list.length) {
|
||||
const [total] = await db.select({ value: sql<number>`count(*)` }).from(device_registration)
|
||||
console.log("[relay] event:no-matching-devices", {
|
||||
type: check.data.eventType,
|
||||
serverID: check.data.serverID,
|
||||
session: check.data.sessionID,
|
||||
secretHash: `${key.slice(0, 12)}...`,
|
||||
totalDevices: Number(total?.value ?? 0),
|
||||
})
|
||||
|
||||
return c.json({
|
||||
ok: true,
|
||||
sent: 0,
|
||||
failed: 0,
|
||||
})
|
||||
}
|
||||
|
||||
const out = await Promise.all(
|
||||
list.map(async (row) => {
|
||||
const env = row.apns_env === "sandbox" ? "sandbox" : "production"
|
||||
const payload = {
|
||||
token: row.device_token,
|
||||
bundle: row.bundle_id,
|
||||
title: check.data.title ?? title(check.data.eventType),
|
||||
body: check.data.body ?? body(check.data.eventType),
|
||||
data: {
|
||||
serverID: check.data.serverID,
|
||||
eventType: check.data.eventType,
|
||||
sessionID: check.data.sessionID,
|
||||
},
|
||||
}
|
||||
const first = await send({ ...payload, env })
|
||||
if (first.ok || !bad(first.error)) {
|
||||
if (!first.ok) {
|
||||
console.log("[relay] send:error", {
|
||||
token: tail(row.device_token),
|
||||
env,
|
||||
error: first.error,
|
||||
})
|
||||
}
|
||||
return first
|
||||
}
|
||||
|
||||
const alt = flip(env)
|
||||
console.log("[relay] send:retry-env", {
|
||||
token: tail(row.device_token),
|
||||
from: env,
|
||||
to: alt,
|
||||
})
|
||||
const second = await send({ ...payload, env: alt })
|
||||
if (!second.ok) {
|
||||
console.log("[relay] send:error", {
|
||||
token: tail(row.device_token),
|
||||
env: alt,
|
||||
error: second.error,
|
||||
})
|
||||
return second
|
||||
}
|
||||
|
||||
await db
|
||||
.update(device_registration)
|
||||
.set({ apns_env: alt, updated_at: Date.now() })
|
||||
.where(
|
||||
and(
|
||||
eq(device_registration.secret_hash, row.secret_hash),
|
||||
eq(device_registration.device_token, row.device_token),
|
||||
),
|
||||
)
|
||||
|
||||
console.log("[relay] send:env-updated", {
|
||||
token: tail(row.device_token),
|
||||
env: alt,
|
||||
})
|
||||
return second
|
||||
}),
|
||||
)
|
||||
|
||||
const now = Date.now()
|
||||
await db.insert(delivery_log).values(
|
||||
out.map((item) => ({
|
||||
id: randomUUID(),
|
||||
secret_hash: key,
|
||||
event_type: check.data.eventType,
|
||||
session_id: check.data.sessionID,
|
||||
status: item.ok ? "sent" : "failed",
|
||||
error: item.error,
|
||||
created_at: now,
|
||||
})),
|
||||
)
|
||||
|
||||
const sent = out.filter((item) => item.ok).length
|
||||
console.log("[relay] event:done", {
|
||||
type: check.data.eventType,
|
||||
session: check.data.sessionID,
|
||||
sent,
|
||||
failed: out.length - sent,
|
||||
})
|
||||
return c.json({
|
||||
ok: true,
|
||||
sent,
|
||||
failed: out.length - sent,
|
||||
})
|
||||
})
|
||||
|
||||
await setup()
|
||||
|
||||
if (import.meta.main) {
|
||||
Bun.serve({
|
||||
port: env.PORT,
|
||||
fetch: app.fetch,
|
||||
})
|
||||
console.log(`apn-relay listening on http://0.0.0.0:${env.PORT}`)
|
||||
}
|
||||
|
||||
export { app }
|
||||
@@ -0,0 +1,35 @@
|
||||
import { bigint, index, mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"
|
||||
|
||||
export const device_registration = mysqlTable(
|
||||
"device_registration",
|
||||
{
|
||||
id: varchar("id", { length: 36 }).primaryKey(),
|
||||
secret_hash: varchar("secret_hash", { length: 64 }).notNull(),
|
||||
device_token: varchar("device_token", { length: 255 }).notNull(),
|
||||
bundle_id: varchar("bundle_id", { length: 255 }).notNull(),
|
||||
apns_env: varchar("apns_env", { length: 16 }).notNull().default("production"),
|
||||
created_at: bigint("created_at", { mode: "number" }).notNull(),
|
||||
updated_at: bigint("updated_at", { mode: "number" }).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
uniqueIndex("device_registration_secret_token_idx").on(table.secret_hash, table.device_token),
|
||||
index("device_registration_secret_hash_idx").on(table.secret_hash),
|
||||
],
|
||||
)
|
||||
|
||||
export const delivery_log = mysqlTable(
|
||||
"delivery_log",
|
||||
{
|
||||
id: varchar("id", { length: 36 }).primaryKey(),
|
||||
secret_hash: varchar("secret_hash", { length: 64 }).notNull(),
|
||||
event_type: varchar("event_type", { length: 32 }).notNull(),
|
||||
session_id: varchar("session_id", { length: 255 }).notNull(),
|
||||
status: varchar("status", { length: 16 }).notNull(),
|
||||
error: varchar("error", { length: 1024 }),
|
||||
created_at: bigint("created_at", { mode: "number" }).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index("delivery_log_secret_hash_idx").on(table.secret_hash),
|
||||
index("delivery_log_created_at_idx").on(table.created_at),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
import { sql } from "drizzle-orm"
|
||||
import { db } from "./db"
|
||||
|
||||
export async function setup() {
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS device_registration (
|
||||
id varchar(36) NOT NULL,
|
||||
secret_hash varchar(64) NOT NULL,
|
||||
device_token varchar(255) NOT NULL,
|
||||
bundle_id varchar(255) NOT NULL,
|
||||
apns_env varchar(16) NOT NULL DEFAULT 'production',
|
||||
created_at bigint NOT NULL,
|
||||
updated_at bigint NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY device_registration_secret_token_idx (secret_hash, device_token),
|
||||
KEY device_registration_secret_hash_idx (secret_hash)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS delivery_log (
|
||||
id varchar(36) NOT NULL,
|
||||
secret_hash varchar(64) NOT NULL,
|
||||
event_type varchar(32) NOT NULL,
|
||||
session_id varchar(255) NOT NULL,
|
||||
status varchar(16) NOT NULL,
|
||||
error varchar(1024) NULL,
|
||||
created_at bigint NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY delivery_log_secret_hash_idx (secret_hash),
|
||||
KEY delivery_log_created_at_idx (created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
`)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@tsconfig/bun/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"noUncheckedIndexedAccess": false
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,20 @@ import { SettingsGeneral } from "./settings-general"
|
||||
import { SettingsKeybinds } from "./settings-keybinds"
|
||||
import { SettingsProviders } from "./settings-providers"
|
||||
import { SettingsModels } from "./settings-models"
|
||||
import { SettingsPair } from "./settings-pair"
|
||||
|
||||
export const DialogSettings: Component = () => {
|
||||
export const DialogSettings: Component<{ defaultTab?: string }> = (props) => {
|
||||
const language = useLanguage()
|
||||
const platform = usePlatform()
|
||||
|
||||
return (
|
||||
<Dialog size="x-large" transition>
|
||||
<Tabs orientation="vertical" variant="settings" defaultValue="general" class="h-full settings-dialog">
|
||||
<Tabs
|
||||
orientation="vertical"
|
||||
variant="settings"
|
||||
defaultValue={props.defaultTab ?? "general"}
|
||||
class="h-full settings-dialog"
|
||||
>
|
||||
<Tabs.List>
|
||||
<div class="flex flex-col justify-between h-full w-full">
|
||||
<div class="flex flex-col gap-3 w-full pt-3">
|
||||
@@ -45,6 +51,10 @@ export const DialogSettings: Component = () => {
|
||||
<Icon name="models" />
|
||||
{language.t("settings.models.title")}
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="pair">
|
||||
<Icon name="link" />
|
||||
{language.t("settings.pair.title")}
|
||||
</Tabs.Trigger>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,6 +77,9 @@ export const DialogSettings: Component = () => {
|
||||
<Tabs.Content value="models" class="no-scrollbar">
|
||||
<SettingsModels />
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="pair" class="no-scrollbar">
|
||||
<SettingsPair />
|
||||
</Tabs.Content>
|
||||
</Tabs>
|
||||
</Dialog>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import { type Component, createResource, Show } from "solid-js"
|
||||
import { Icon } from "@opencode-ai/ui/icon"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useGlobalSDK } from "@/context/global-sdk"
|
||||
import { useServer } from "@/context/server"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { SettingsList } from "./settings-list"
|
||||
|
||||
type PairResult =
|
||||
| { enabled: false }
|
||||
| {
|
||||
enabled: true
|
||||
hosts: string[]
|
||||
relayURL?: string
|
||||
serverID?: string
|
||||
relaySecretHash?: string
|
||||
link: string
|
||||
qr: string
|
||||
}
|
||||
|
||||
export const SettingsPair: Component = () => {
|
||||
const language = useLanguage()
|
||||
const globalSDK = useGlobalSDK()
|
||||
const server = useServer()
|
||||
const platform = usePlatform()
|
||||
|
||||
const [data] = createResource(async () => {
|
||||
const url = `${globalSDK.url}/experimental/push/pair`
|
||||
console.debug("[settings-pair] fetching pair data", {
|
||||
serverUrl: globalSDK.url,
|
||||
serverName: server.name,
|
||||
serverKey: server.key,
|
||||
})
|
||||
const f = platform.fetch ?? fetch
|
||||
const res = await f(url)
|
||||
if (!res.ok) {
|
||||
console.debug("[settings-pair] pair endpoint returned non-ok", {
|
||||
status: res.status,
|
||||
serverUrl: globalSDK.url,
|
||||
})
|
||||
return { enabled: false as const }
|
||||
}
|
||||
const result = (await res.json()) as PairResult
|
||||
console.debug("[settings-pair] pair data received", {
|
||||
enabled: result.enabled,
|
||||
serverUrl: globalSDK.url,
|
||||
serverName: server.name,
|
||||
...(result.enabled
|
||||
? {
|
||||
relayURL: result.relayURL,
|
||||
serverID: result.serverID,
|
||||
relaySecretHash: result.relaySecretHash,
|
||||
hostCount: result.hosts.length,
|
||||
hosts: result.hosts,
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
return result
|
||||
})
|
||||
|
||||
return (
|
||||
<div class="flex flex-col gap-6 py-4 px-5">
|
||||
<div class="flex flex-col gap-1">
|
||||
<h2 class="text-16-semibold text-text-strong">{language.t("settings.pair.title")}</h2>
|
||||
<p class="text-13-regular text-text-weak">{language.t("settings.pair.description")}</p>
|
||||
</div>
|
||||
|
||||
<Show when={data.loading}>
|
||||
<SettingsList>
|
||||
<div class="flex items-center justify-center py-12">
|
||||
<span class="text-14-regular text-text-weak">{language.t("settings.pair.loading")}</span>
|
||||
</div>
|
||||
</SettingsList>
|
||||
</Show>
|
||||
|
||||
<Show when={data.error}>
|
||||
<SettingsList>
|
||||
<div class="flex flex-col items-center justify-center py-12 gap-3 text-center">
|
||||
<Icon name="warning" size="large" />
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-14-medium text-text-strong">{language.t("settings.pair.error.title")}</span>
|
||||
<span class="text-13-regular text-text-weak max-w-md">
|
||||
{language.t("settings.pair.error.description")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsList>
|
||||
</Show>
|
||||
|
||||
<Show when={!data.loading && !data.error && data()}>
|
||||
{(result) => (
|
||||
<Show
|
||||
when={result().enabled && result()}
|
||||
fallback={
|
||||
<SettingsList>
|
||||
<div class="flex flex-col items-center justify-center py-12 gap-3 text-center">
|
||||
<Icon name="link" size="large" />
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-14-medium text-text-strong">{language.t("settings.pair.disabled.title")}</span>
|
||||
<span class="text-13-regular text-text-weak max-w-md">
|
||||
{language.t("settings.pair.disabled.description")}
|
||||
</span>
|
||||
</div>
|
||||
<code class="text-12-regular text-text-weak bg-surface-inset px-3 py-1.5 rounded mt-1">
|
||||
opencode serve --relay-url <url> --relay-secret <secret>
|
||||
</code>
|
||||
</div>
|
||||
</SettingsList>
|
||||
}
|
||||
>
|
||||
{(pair) => {
|
||||
const p = pair() as PairResult & { enabled: true }
|
||||
return (
|
||||
<SettingsList>
|
||||
<div class="flex flex-col items-center py-8 gap-4">
|
||||
<Show when={server.list.length > 1 || p.relayURL}>
|
||||
<div class="flex flex-col gap-1.5 w-full max-w-sm text-left">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-12-medium text-text-weak shrink-0">
|
||||
{language.t("settings.pair.server.label")}
|
||||
</span>
|
||||
<code class="text-12-regular text-text-default bg-surface-inset px-2 py-0.5 rounded truncate">
|
||||
{server.name}
|
||||
</code>
|
||||
</div>
|
||||
<Show when={p.relayURL}>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-12-medium text-text-weak shrink-0">
|
||||
{language.t("settings.pair.relay.label")}
|
||||
</span>
|
||||
<code class="text-12-regular text-text-default bg-surface-inset px-2 py-0.5 rounded truncate">
|
||||
{p.relayURL}
|
||||
</code>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={p.relaySecretHash}>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-12-medium text-text-weak shrink-0">
|
||||
{language.t("settings.pair.secret.label")}
|
||||
</span>
|
||||
<code class="text-12-regular text-text-default bg-surface-inset px-2 py-0.5 rounded truncate">
|
||||
{p.relaySecretHash}
|
||||
</code>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
<img src={p.qr} alt="Pairing QR code" class="w-64 h-64" />
|
||||
<div class="flex flex-col gap-1 text-center max-w-sm">
|
||||
<span class="text-14-medium text-text-strong">
|
||||
{language.t("settings.pair.instructions.title")}
|
||||
</span>
|
||||
<span class="text-13-regular text-text-weak">
|
||||
{language.t("settings.pair.instructions.description")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsList>
|
||||
)
|
||||
}}
|
||||
</Show>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -28,6 +28,7 @@ export const dict = {
|
||||
"command.provider.connect": "Connect provider",
|
||||
"command.server.switch": "Switch server",
|
||||
"command.settings.open": "Open settings",
|
||||
"command.pair.show": "Pair mobile device",
|
||||
"command.session.previous": "Previous session",
|
||||
"command.session.next": "Next session",
|
||||
"command.session.previous.unseen": "Previous unread session",
|
||||
@@ -870,6 +871,20 @@ export const dict = {
|
||||
"settings.providers.tag.config": "Config",
|
||||
"settings.providers.tag.custom": "Custom",
|
||||
"settings.providers.tag.other": "Other",
|
||||
"settings.pair.title": "Pair",
|
||||
"settings.pair.description": "Pair a mobile device for push notifications.",
|
||||
"settings.pair.loading": "Loading pairing info...",
|
||||
"settings.pair.error.title": "Could not load pairing info",
|
||||
"settings.pair.error.description": "Check that the server is reachable and try again.",
|
||||
"settings.pair.disabled.title": "Push relay is not enabled",
|
||||
"settings.pair.disabled.description": "Start the server with push relay options to enable mobile pairing.",
|
||||
"settings.pair.server.label": "Server",
|
||||
"settings.pair.relay.label": "Relay",
|
||||
"settings.pair.secret.label": "Secret",
|
||||
"settings.pair.instructions.title": "Scan with the OpenCode Control app",
|
||||
"settings.pair.instructions.description":
|
||||
"Open the OpenCode Control app and scan this QR code to pair your device for push notifications.",
|
||||
|
||||
"settings.models.title": "Models",
|
||||
"settings.models.description": "Model settings will be configurable here.",
|
||||
"settings.agents.title": "Agents",
|
||||
|
||||
@@ -1061,6 +1061,13 @@ export default function Layout(props: ParentProps) {
|
||||
keybind: "mod+comma",
|
||||
onSelect: () => openSettings(),
|
||||
},
|
||||
{
|
||||
id: "pair.show",
|
||||
title: language.t("command.pair.show"),
|
||||
category: language.t("command.category.settings"),
|
||||
slash: "pair",
|
||||
onSelect: () => openSettings("pair"),
|
||||
},
|
||||
{
|
||||
id: "session.previous",
|
||||
title: language.t("command.session.previous"),
|
||||
@@ -1213,11 +1220,11 @@ export default function Layout(props: ParentProps) {
|
||||
})
|
||||
}
|
||||
|
||||
function openSettings() {
|
||||
function openSettings(defaultTab?: string) {
|
||||
const run = ++dialogRun
|
||||
void import("@/components/dialog-settings").then((x) => {
|
||||
if (dialogDead || dialogRun !== run) return
|
||||
dialog.show(() => <x.DialogSettings />)
|
||||
dialog.show(() => <x.DialogSettings defaultTab={defaultTab} />)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,10 @@ declare module "virtual:opencode-server" {
|
||||
export const listen: typeof import("../../../opencode/dist/types/src/node").Server.listen
|
||||
export type Listener = import("../../../opencode/dist/types/src/node").Server.Listener
|
||||
}
|
||||
export namespace PushRelay {
|
||||
export const start: typeof import("../../../opencode/dist/types/src/node").PushRelay.start
|
||||
export const stop: typeof import("../../../opencode/dist/types/src/node").PushRelay.stop
|
||||
}
|
||||
export namespace Config {
|
||||
export const get: typeof import("../../../opencode/dist/types/src/node").Config.get
|
||||
export type Info = import("../../../opencode/dist/types/src/node").Config.Info
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
import { randomBytes } from "node:crypto"
|
||||
import { app } from "electron"
|
||||
import { DEFAULT_SERVER_URL_KEY, WSL_ENABLED_KEY } from "./constants"
|
||||
import { getUserShell, loadShellEnv } from "./shell-env"
|
||||
import { getStore } from "./store"
|
||||
|
||||
const DEFAULT_RELAY_URL = "https://apn.dev.opencode.ai"
|
||||
const RELAY_SECRET_KEY = "relaySecret"
|
||||
|
||||
function getOrCreateRelaySecret(): string {
|
||||
const existing = getStore().get(RELAY_SECRET_KEY)
|
||||
if (typeof existing === "string" && existing.length > 0) return existing
|
||||
const secret = randomBytes(18).toString("base64url")
|
||||
getStore().set(RELAY_SECRET_KEY, secret)
|
||||
return secret
|
||||
}
|
||||
|
||||
export type WslConfig = { enabled: boolean }
|
||||
|
||||
export type HealthCheck = { wait: Promise<void> }
|
||||
@@ -32,7 +44,7 @@ export function setWslConfig(config: WslConfig) {
|
||||
|
||||
export async function spawnLocalServer(hostname: string, port: number, password: string) {
|
||||
prepareServerEnv(password)
|
||||
const { Log, Server } = await import("virtual:opencode-server")
|
||||
const { Log, Server, PushRelay } = await import("virtual:opencode-server")
|
||||
await Log.init({ level: "WARN" })
|
||||
const listener = await Server.listen({
|
||||
port,
|
||||
@@ -41,6 +53,18 @@ export async function spawnLocalServer(hostname: string, port: number, password:
|
||||
password,
|
||||
})
|
||||
|
||||
const relayURL = (process.env.OPENCODE_EXPERIMENTAL_PUSH_RELAY_URL ?? DEFAULT_RELAY_URL).trim()
|
||||
const relaySecretInput = (process.env.OPENCODE_EXPERIMENTAL_PUSH_RELAY_SECRET ?? "").trim()
|
||||
const relaySecret = relaySecretInput || getOrCreateRelaySecret()
|
||||
if (relayURL && relaySecret) {
|
||||
PushRelay.start({
|
||||
relayURL,
|
||||
relaySecret,
|
||||
hostname,
|
||||
port: listener.port,
|
||||
})
|
||||
}
|
||||
|
||||
const wait = (async () => {
|
||||
const url = `http://${hostname}:${port}`
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"plugins": {
|
||||
"figma": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
dist/
|
||||
web-build/
|
||||
expo-env.d.ts
|
||||
|
||||
# Native
|
||||
.kotlin/
|
||||
*.orig.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
|
||||
# Metro
|
||||
.metro-health-check*
|
||||
|
||||
# debug
|
||||
npm-debug.*
|
||||
yarn-debug.*
|
||||
yarn-error.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
app-example
|
||||
|
||||
# generated native folders
|
||||
/ios
|
||||
/android
|
||||
@@ -0,0 +1,183 @@
|
||||
# mobile-voice Agent Guide
|
||||
|
||||
This file defines package-specific guidance for agents working in `packages/mobile-voice`.
|
||||
|
||||
## Scope And Precedence
|
||||
|
||||
- Follow root `AGENTS.md` first.
|
||||
- This file overrides root guidance for this package when rules conflict.
|
||||
- If additional local guides are added later, treat the closest guide as highest priority.
|
||||
|
||||
## Project Overview
|
||||
|
||||
- Expo + React Native app for voice dictation and OpenCode session monitoring.
|
||||
- Uses native/device-heavy modules such as `whisper.rn`, `react-native-audio-api`, `expo-notifications`, and `expo-camera`.
|
||||
- Development builds are required for native module changes.
|
||||
|
||||
## Commands
|
||||
|
||||
Run all commands from `packages/mobile-voice`.
|
||||
|
||||
- Install deps: `bun install`
|
||||
- Start Metro: `bun run start`
|
||||
- Start dev client server (recommended): `bunx expo start --dev-client --clear --host lan`
|
||||
- iOS run: `bun run ios`
|
||||
- Android run: `bun run android`
|
||||
- Lint: `bun run lint`
|
||||
- Typecheck: `bun run typecheck`
|
||||
- Expo doctor: `bunx expo-doctor`
|
||||
- Dependency compatibility check: `bunx expo install --check`
|
||||
- Export bundle smoke test: `bunx expo export --platform ios --clear`
|
||||
|
||||
## Build / Verification Expectations
|
||||
|
||||
- For JS-only changes: run `bun run lint` and verify app behavior via dev client.
|
||||
- For TS-heavy refactors: run `bun run typecheck` in addition to lint.
|
||||
- For native dependency/config/plugin changes: rebuild dev client via EAS before validation.
|
||||
- If notifications, camera, microphone, or audio-session behavior changes, verify on a physical iOS device.
|
||||
- Do not claim a fix unless you validated in Metro logs and app runtime behavior.
|
||||
|
||||
## Single-Test Guidance
|
||||
|
||||
- This package currently has no dedicated unit test script.
|
||||
- Use targeted validation commands instead:
|
||||
- `bun run lint`
|
||||
- `bun run typecheck`
|
||||
- `bunx expo export --platform ios --clear`
|
||||
- manual runtime test in dev client
|
||||
|
||||
## Architecture Priorities
|
||||
|
||||
- Keep screens focused on composition and orchestration. Once a screen owns multiple workflows, extract hooks/components before adding more local state.
|
||||
- Prefer extracting pure helpers and config objects before introducing new stores or abstractions.
|
||||
- Treat `src/app/index.tsx` as a composition root, not as the permanent home for onboarding, dictation, monitoring, pairing, persistence, and all UI details.
|
||||
- Avoid mirrored `state + ref` pairs unless they are needed for imperative native APIs, race cancellation, or subscription callbacks.
|
||||
|
||||
## Code Style And Patterns
|
||||
|
||||
### Formatting / Structure
|
||||
|
||||
- Preserve existing style (`semi: false`, concise JSX, stable import grouping).
|
||||
- Keep UI changes localized and behavior-preserving; avoid unrelated formatting churn.
|
||||
- Prefer feature-adjacent hooks/components over growing a single screen file.
|
||||
|
||||
### React State / Effects
|
||||
|
||||
- Effects are for subscriptions, timers, persistence, network I/O, and native bridge setup/cleanup.
|
||||
- Do not add `useEffect` just to derive render data from props or state. Derive during render instead.
|
||||
- Prefer one source of truth. If a value can be computed from existing state, do not store it separately.
|
||||
- Use `useMemo` only when computation is expensive or stable identity actually matters.
|
||||
- Use `useCallback` only when stable function identity matters for dependencies, cleanup, or memoized children.
|
||||
- When UI branches are driven by a small finite state, prefer config tables/objects over long nested ternaries.
|
||||
|
||||
### Types
|
||||
|
||||
- Avoid `any`; prefer local type aliases for component state and network payloads.
|
||||
- Keep exported/shared boundaries typed explicitly.
|
||||
- Parse persisted and network payloads as `unknown` first, then validate before use.
|
||||
- Use discriminated unions for UI modes/status where practical.
|
||||
|
||||
### Naming
|
||||
|
||||
- Prefer short, readable names consistent with nearby code.
|
||||
- Keep naming aligned with existing app state keys (`monitorStatus`, `activeSessionId`, etc.).
|
||||
|
||||
### Error Handling / Logging
|
||||
|
||||
- Fail gracefully in UI (alerts, disabled actions, fallback text).
|
||||
- Avoid bare `catch {}` or `.catch(() => {})` for meaningful work. If failure is intentionally best-effort, leave a short comment or use a helper that makes that explicit.
|
||||
- Log actionable diagnostics for runtime workflows such as server health checks, relay registration, and notification token lifecycle.
|
||||
- Never log secrets or full APNs tokens.
|
||||
- Keep hot-path logging behind `__DEV__` when possible.
|
||||
|
||||
### Network / Relay Integration
|
||||
|
||||
- Normalize and validate URLs before storing server configs.
|
||||
- Use `AbortController` or request IDs for overlapping requests, streams, and polling.
|
||||
- Keep relay registration idempotent.
|
||||
- Guard duplicate scan/add flows to avoid repeated server entries.
|
||||
|
||||
### Notifications / APNs
|
||||
|
||||
- This package currently assumes APNs relay registration uses the `production` environment only. Do not add environment switching unless explicitly requested.
|
||||
- On registration changes, ensure old token unregister flow remains intact.
|
||||
- Treat permission failures as non-fatal and degrade to foreground monitoring when needed.
|
||||
|
||||
### Performance / RN
|
||||
|
||||
- Validate performance-sensitive changes in a dev client or release build, not only Metro dev mode.
|
||||
- During recording and monitoring flows, keep JS-thread work light.
|
||||
- Prefer Reanimated/native-thread-friendly animations for motion.
|
||||
- For small menus a `ScrollView` is fine; if a list grows beyond a small bounded menu, move to `FlatList` or `FlashList`.
|
||||
|
||||
## Lint / Quality Bar
|
||||
|
||||
- Keep hooks lint warnings clean before finishing.
|
||||
- Treat `any`, `no-console`, complexity, and max-lines warnings as refactor prompts, not noise to suppress.
|
||||
- Do not disable React Hooks lint rules inline unless there is a documented native-interop reason.
|
||||
- When introducing new persistence or network payloads, add or reuse a parser instead of scattering casts.
|
||||
|
||||
## Native-Module Safety
|
||||
|
||||
- If adding a native module, ensure it is in `package.json` with an SDK-compatible version.
|
||||
- Rebuild the dev client after native module additions or changes.
|
||||
- For optional native capability usage, prefer runtime fallback paths instead of hard crashes.
|
||||
|
||||
## Expo Native Config (EAS)
|
||||
|
||||
- Treat `packages/mobile-voice/app.json` as the source of truth for iOS native metadata in EAS cloud builds.
|
||||
- Do not rely on manual edits in `ios/mobilevoice/Info.plist`, entitlements files, or `PrivacyInfo.xcprivacy`; for this package they are generated outputs.
|
||||
- Keep generated native folders untracked in git (`/ios`, `/android`) to avoid mixed CNG/bare behavior during EAS builds.
|
||||
- Put App Store compliance and permission metadata in app config using these fields:
|
||||
- `expo.ios.infoPlist` for Info.plist keys (usage strings, ATS, Bonjour, and related keys).
|
||||
- `expo.ios.config.usesNonExemptEncryption` for export-compliance encryption declaration.
|
||||
- `expo.ios.entitlements` for iOS entitlements.
|
||||
- `expo.ios.privacyManifests` for Apple privacy manifest declarations.
|
||||
- Keep `app.json` entries explicit and review-friendly:
|
||||
- Permission descriptions should be complete, product-specific sentences.
|
||||
- Compliance keys should be set intentionally rather than relying on implicit defaults.
|
||||
- Preserve existing JSON style in this package (concise arrays and stable key grouping).
|
||||
- After native config changes, verify resolved config with `bunx expo config --type prebuild --json` and check the resulting `ios` fields.
|
||||
|
||||
Example shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"expo": {
|
||||
"ios": {
|
||||
"infoPlist": {
|
||||
"NSCameraUsageDescription": "...",
|
||||
"NSMicrophoneUsageDescription": "..."
|
||||
},
|
||||
"config": {
|
||||
"usesNonExemptEncryption": false
|
||||
},
|
||||
"entitlements": {
|
||||
"com.apple.developer.kernel.extended-virtual-addressing": true
|
||||
},
|
||||
"privacyManifests": {
|
||||
"NSPrivacyAccessedAPITypes": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- Black screen + "No script URL provided" often means a stale dev client binary.
|
||||
- `expo-doctor` duplicate module warnings may appear in Bun workspaces; prioritize runtime verification.
|
||||
- `expo lint` may auto-generate `eslint.config.js`; do not commit accidental generated config unless requested.
|
||||
|
||||
## Before Finishing
|
||||
|
||||
- Run `bun run lint`.
|
||||
- If behavior could break startup, run `bunx expo export --platform ios --clear`.
|
||||
- Confirm no accidental config side effects were introduced.
|
||||
- Summarize what was verified on-device vs only in tooling.
|
||||
|
||||
|
||||
- Dev build (internal/dev client):
|
||||
- bunx eas build --profile development --platform ios
|
||||
- Production build + auto-submit:
|
||||
- bunx eas build --profile production --platform ios --auto-submit
|
||||
@@ -0,0 +1,39 @@
|
||||
# Mobile Voice
|
||||
|
||||
Expo app for voice dictation and OpenCode session monitoring.
|
||||
|
||||
## Current monitoring behavior
|
||||
|
||||
- Foreground: app reads OpenCode SSE (`GET /event`) and updates monitor status live.
|
||||
- Background/terminated: app relies on APNs notifications sent by `apn-relay`.
|
||||
- The app registers its native APNs device token with relay route `POST /v1/device/register`.
|
||||
|
||||
## App requirements
|
||||
|
||||
- Use a development build or production build (not Expo Go).
|
||||
- `expo-notifications` plugin is enabled with `enableBackgroundRemoteNotifications: true`.
|
||||
- Notification permission must be granted.
|
||||
|
||||
## Server entry fields in app
|
||||
|
||||
When adding a server, provide:
|
||||
|
||||
- OpenCode URL
|
||||
- APN relay URL
|
||||
- Relay shared secret
|
||||
|
||||
Default APN relay URL: `https://apn.dev.opencode.ai`
|
||||
|
||||
The app uses these values to:
|
||||
|
||||
- send prompts to OpenCode
|
||||
- register/unregister APNs token with relay
|
||||
- receive background push updates for monitored sessions
|
||||
|
||||
## Local dev
|
||||
|
||||
```bash
|
||||
npx expo start
|
||||
```
|
||||
|
||||
Use your machine LAN IP / reachable host values for OpenCode and relay when testing on a physical device.
|
||||
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "Control",
|
||||
"slug": "control",
|
||||
"version": "1.0.2",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/new-control.png",
|
||||
"scheme": "mobilevoice",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"ios": {
|
||||
"icon": "./assets/new-control.png",
|
||||
"bundleIdentifier": "com.anomalyco.mobilevoice",
|
||||
"config": {
|
||||
"usesNonExemptEncryption": false
|
||||
},
|
||||
"entitlements": {
|
||||
"com.apple.developer.kernel.extended-virtual-addressing": true
|
||||
},
|
||||
"infoPlist": {
|
||||
"NSMicrophoneUsageDescription": "Control uses the microphone while you hold Record to turn your speech into text for an OpenCode session.",
|
||||
"NSCameraUsageDescription": "Control uses the camera to scan the OpenCode pairing QR code shown on your computer.",
|
||||
"NSLocalNetworkUsageDescription": "Control uses your local network to discover and connect to OpenCode servers running on your computer.",
|
||||
"NSBonjourServices": ["_http._tcp."],
|
||||
"NSAppTransportSecurity": {
|
||||
"NSAllowsLocalNetworking": true,
|
||||
"NSExceptionDomains": {
|
||||
"100.64.0.0/10": {
|
||||
"NSExceptionAllowsInsecureHTTPLoads": true
|
||||
},
|
||||
"ts.net": {
|
||||
"NSIncludesSubdomains": true,
|
||||
"NSExceptionAllowsInsecureHTTPLoads": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"backgroundColor": "#1a1a1a",
|
||||
"foregroundImage": "./assets/images/android-icon-foreground.png",
|
||||
"backgroundImage": "./assets/images/android-icon-background.png",
|
||||
"monochromeImage": "./assets/images/android-icon-monochrome.png"
|
||||
},
|
||||
"permissions": [
|
||||
"RECORD_AUDIO",
|
||||
"POST_NOTIFICATIONS",
|
||||
"android.permission.FOREGROUND_SERVICE",
|
||||
"android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK",
|
||||
"android.permission.RECORD_AUDIO",
|
||||
"android.permission.MODIFY_AUDIO_SETTINGS",
|
||||
"android.permission.ACCESS_NETWORK_STATE",
|
||||
"android.permission.ACCESS_WIFI_STATE",
|
||||
"android.permission.CHANGE_WIFI_MULTICAST_STATE"
|
||||
],
|
||||
"predictiveBackGestureEnabled": false
|
||||
},
|
||||
"web": {
|
||||
"output": "static",
|
||||
"favicon": "./assets/images/favicon.png"
|
||||
},
|
||||
"plugins": [
|
||||
"expo-router",
|
||||
[
|
||||
"expo-splash-screen",
|
||||
{
|
||||
"backgroundColor": "#121212",
|
||||
"android": {
|
||||
"image": "./assets/images/splash-icon.png",
|
||||
"imageWidth": 76
|
||||
}
|
||||
}
|
||||
],
|
||||
"react-native-audio-api",
|
||||
"expo-asset",
|
||||
"expo-audio",
|
||||
[
|
||||
"expo-notifications",
|
||||
{
|
||||
"enableBackgroundRemoteNotifications": true,
|
||||
"sounds": ["./assets/sounds/alert.wav"]
|
||||
}
|
||||
]
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true,
|
||||
"reactCompiler": true
|
||||
},
|
||||
"extra": {
|
||||
"router": {},
|
||||
"eas": {
|
||||
"projectId": "50b3dac3-8b5e-4142-b749-65ecf7b2904d"
|
||||
}
|
||||
},
|
||||
"owner": "anomaly-co",
|
||||
"runtimeVersion": "1.0.2",
|
||||
"updates": {
|
||||
"url": "https://u.expo.dev/50b3dac3-8b5e-4142-b749-65ecf7b2904d"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 755 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="652" height="606" viewBox="0 0 652 606" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M353.554 0H298.446C273.006 0 249.684 14.6347 237.962 37.9539L4.37994 502.646C-1.04325 513.435 -1.45067 526.178 3.2716 537.313L22.6123 582.918C34.6475 611.297 72.5404 614.156 88.4414 587.885L309.863 222.063C313.34 216.317 319.439 212.826 326 212.826C332.561 212.826 338.659 216.317 342.137 222.063L563.559 587.885C579.46 614.156 617.352 611.297 629.388 582.918L648.728 537.313C653.451 526.178 653.043 513.435 647.62 502.646L414.038 37.9539C402.316 14.6347 378.994 0 353.554 0Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 608 B |
|
After Width: | Height: | Size: 52 KiB |
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fill" : {
|
||||
"automatic-gradient" : "extended-srgb:0.00000,0.47843,1.00000,1.00000"
|
||||
},
|
||||
"groups" : [
|
||||
{
|
||||
"layers" : [
|
||||
{
|
||||
"image-name" : "expo-symbol 2.svg",
|
||||
"name" : "expo-symbol 2",
|
||||
"position" : {
|
||||
"scale" : 1,
|
||||
"translation-in-points" : [
|
||||
1.1008400065293245e-05,
|
||||
-16.046875
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"image-name" : "grid.png",
|
||||
"name" : "grid"
|
||||
}
|
||||
],
|
||||
"shadow" : {
|
||||
"kind" : "neutral",
|
||||
"opacity" : 0.5
|
||||
},
|
||||
"translucency" : {
|
||||
"enabled" : true,
|
||||
"value" : 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"supported-platforms" : {
|
||||
"circles" : [
|
||||
"watchOS"
|
||||
],
|
||||
"squares" : "shared"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 755 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 755 KiB |
|
After Width: | Height: | Size: 324 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 215 B |
|
After Width: | Height: | Size: 347 B |
|
After Width: | Height: | Size: 468 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 343 B |
|
After Width: | Height: | Size: 479 B |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 4.1 MiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,328 @@
|
||||
# Live Activity Implementation Plan
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ iPhone Lock Screen / Dynamic Island │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ Live Activity (expo-widgets) │ │
|
||||
│ │ "Installing dependencies..." ● Working │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ ▲ local update (foreground) ▲ APNs push (bg) │
|
||||
│ │ │ │
|
||||
│ ┌──────┴─────┐ │ │
|
||||
│ │ SSE stream │ │ │
|
||||
│ │ /event │ │ │
|
||||
│ └──────┬─────┘ │ │
|
||||
└─────────┼──────────────────────────────┼────────────────────┘
|
||||
│ │
|
||||
▼ │
|
||||
┌──────────────────┐ ┌────────┴─────────┐
|
||||
│ OpenCode Server │──event──> │ APN Relay │
|
||||
│ (push-relay.ts) │ │ /v1/activity/* │
|
||||
│ GlobalBus │ │ apns.ts │
|
||||
└──────────────────┘ └──────────────────┘
|
||||
```
|
||||
|
||||
**Foreground**: App receives SSE events, updates the Live Activity locally via `instance.update()`.
|
||||
**Background**: OpenCode server fires events to the relay, relay sends `liveactivity` APNs pushes with `content-state`, iOS updates the widget.
|
||||
**Push-to-start**: Relay sends a `start` push to begin a Live Activity even when the app hasn't initiated one.
|
||||
|
||||
## Content-State Shape
|
||||
|
||||
```typescript
|
||||
type SessionActivityProps = {
|
||||
status: "working" | "retry" | "permission" | "complete" | "error"
|
||||
sessionTitle: string // e.g. "Fix auth bug"
|
||||
lastMessage: string // truncated ~120 chars, e.g. "Installing dependencies..."
|
||||
retryInfo: string | null // e.g. "Retry 2/5 in 8s" when status is "retry"
|
||||
}
|
||||
```
|
||||
|
||||
This is intentionally lean -- it keeps APNs payload size well under the 4KB limit.
|
||||
|
||||
## Dynamic Island / Lock Screen Layout
|
||||
|
||||
| Slot | Content |
|
||||
| ------------------------ | ---------------------------------------------- |
|
||||
| **Banner** (Lock Screen) | Session title, status badge, last message text |
|
||||
| **Compact leading** | App icon or "OC" text |
|
||||
| **Compact trailing** | Status word ("Working", "Done", "Needs input") |
|
||||
| **Minimal** | Small status dot/icon |
|
||||
| **Expanded leading** | Session title + status |
|
||||
| **Expanded trailing** | Time elapsed or ETA |
|
||||
| **Expanded bottom** | Last message text, retry info if applicable |
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Core Live Activity (App-Initiated, Local + Push Updates)
|
||||
|
||||
This phase delivers the end-to-end working feature.
|
||||
|
||||
### 1a. Install and configure expo-widgets
|
||||
|
||||
**Package**: `mobile-voice`
|
||||
|
||||
- Add `expo-widgets` and `@expo/ui` to dependencies
|
||||
- Add the plugin to `app.json`:
|
||||
```json
|
||||
[
|
||||
"expo-widgets",
|
||||
{
|
||||
"enablePushNotifications": true,
|
||||
"widgets": [
|
||||
{
|
||||
"name": "SessionActivity",
|
||||
"displayName": "OpenCode Session",
|
||||
"description": "Live session monitoring on Lock Screen and Dynamic Island"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
- Add `NSSupportsLiveActivities: true` and `NSSupportsLiveActivitiesFrequentUpdates: true` to `expo.ios.infoPlist` in `app.json`
|
||||
- Requires a new EAS dev build after this step
|
||||
|
||||
### 1b. Create the Live Activity component
|
||||
|
||||
**New file**: `src/widgets/session-activity.tsx`
|
||||
|
||||
- Define `SessionActivityProps` type
|
||||
- Build the `LiveActivityLayout` using `@expo/ui/swift-ui` primitives (`Text`, `VStack`, `HStack`)
|
||||
- Export via `createLiveActivity('SessionActivity', SessionActivity)`
|
||||
- Adapt layout per slot (banner, compact, minimal, expanded)
|
||||
- Use `LiveActivityEnvironment.colorScheme` to handle dark/light
|
||||
|
||||
### 1c. Create a Live Activity management hook
|
||||
|
||||
**New file**: `src/hooks/use-live-activity.ts`
|
||||
|
||||
Responsibilities:
|
||||
|
||||
- `startActivity(sessionTitle, sessionId)` -- calls `SessionActivity.start(props, deepLinkURL)`, stores the instance, gets the push token
|
||||
- `updateActivity(props)` -- calls `instance.update(props)` for foreground SSE-driven updates
|
||||
- `endActivity(finalStatus)` -- calls `instance.end('default', finalProps)`
|
||||
- Manages the per-activity push token lifecycle
|
||||
- Exposes `activityPushToken: string | null` for relay registration
|
||||
- Handles edge cases: activity already running (end previous before starting new), activities disabled by user, iOS version checks
|
||||
|
||||
### 1d. Integrate into useMonitoring
|
||||
|
||||
**File**: `src/hooks/use-monitoring.ts`
|
||||
|
||||
- Import and use `useLiveActivity`
|
||||
- **On `beginMonitoring(job)`**: call `startActivity(sessionTitle, job.sessionID)`
|
||||
- **In the SSE event handler** (foreground): map classified events to `updateActivity()` calls:
|
||||
- `session.status` busy -> `{ status: "working", lastMessage: <latest text> }`
|
||||
- `session.status` retry -> `{ status: "retry", retryInfo: "Retry N in Xs" }`
|
||||
- `permission.asked` -> `{ status: "permission", lastMessage: "Needs your decision" }`
|
||||
- `session.status` idle (complete) -> `endActivity("complete")`
|
||||
- `session.error` -> `endActivity("error")`
|
||||
- **On stop monitoring**: end the activity if still running
|
||||
- **On app background**: stop SSE (already happens), rely on APNs pushes for updates
|
||||
- **On app foreground**: reconnect SSE, sync local activity state with `syncSessionState()`
|
||||
|
||||
### 1e. Register activity push tokens with the relay
|
||||
|
||||
**File**: `src/lib/relay-client.ts`
|
||||
|
||||
- New function: `registerActivityToken(input)` -- calls new relay endpoint `POST /v1/activity/register`
|
||||
```typescript
|
||||
registerActivityToken(input: {
|
||||
relayBaseURL: string
|
||||
secret: string
|
||||
activityToken: string
|
||||
sessionID: string
|
||||
bundleId?: string
|
||||
}): Promise<void>
|
||||
```
|
||||
- New function: `unregisterActivityToken(input)` -- cleanup
|
||||
```typescript
|
||||
unregisterActivityToken(input: {
|
||||
relayBaseURL: string
|
||||
secret: string
|
||||
sessionID: string
|
||||
}): Promise<void>
|
||||
```
|
||||
|
||||
**File**: `src/hooks/use-live-activity.ts` or `use-monitoring.ts`
|
||||
|
||||
- When `activityPushToken` becomes available after `startActivity()`, send it to the relay alongside the `sessionID`
|
||||
- On activity end, unregister the token
|
||||
|
||||
### 1f. Extend the APN relay for Live Activity pushes
|
||||
|
||||
**Package**: `apn-relay`
|
||||
|
||||
New endpoint: `POST /v1/activity/register`
|
||||
|
||||
```typescript
|
||||
{
|
||||
secret: string,
|
||||
sessionID: string,
|
||||
activityToken: string, // the per-activity push token
|
||||
bundleId?: string
|
||||
}
|
||||
```
|
||||
|
||||
New endpoint: `POST /v1/activity/unregister`
|
||||
|
||||
```typescript
|
||||
{
|
||||
secret: string,
|
||||
sessionID: string
|
||||
}
|
||||
```
|
||||
|
||||
New DB table: `activity_registration`
|
||||
|
||||
```sql
|
||||
id TEXT PRIMARY KEY,
|
||||
secret_hash TEXT NOT NULL,
|
||||
session_id TEXT NOT NULL,
|
||||
activity_token TEXT NOT NULL,
|
||||
bundle_id TEXT NOT NULL,
|
||||
apns_env TEXT NOT NULL DEFAULT 'production',
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
UNIQUE(secret_hash, session_id)
|
||||
```
|
||||
|
||||
Modified: `POST /v1/event` handler
|
||||
|
||||
- After sending the regular alert push (existing behavior), also check `activity_registration` for matching `(secret_hash, session_id)`
|
||||
- If a registration exists, send a second push with:
|
||||
- `apns-push-type: liveactivity`
|
||||
- `apns-topic: {bundleId}.push-type.liveactivity`
|
||||
- Payload with `content-state` containing the `SessionActivityProps` fields
|
||||
- `event: "update"` for progress, `event: "end"` for complete/error
|
||||
|
||||
New function in `apns.ts`: `sendLiveActivityUpdate(input)`
|
||||
|
||||
- Separate from the existing `send()` function
|
||||
- Uses `liveactivity` push type headers
|
||||
- Constructs `content-state` payload format
|
||||
|
||||
### 1g. Extend the OpenCode server push-relay for richer events
|
||||
|
||||
**File**: `packages/opencode/src/server/push-relay.ts`
|
||||
|
||||
- Extend `Type` union: `"complete" | "permission" | "error" | "progress"`
|
||||
- Add cases to `map()` function:
|
||||
- `session.status` with `type: "busy"` -> `{ type: "progress", sessionID }`
|
||||
- `session.status` with `type: "retry"` -> `{ type: "progress", sessionID }` (with retry metadata)
|
||||
- `message.updated` where the message has tool-use or assistant text -> `{ type: "progress", sessionID }` (throttled)
|
||||
- Add to `notify()` / `post()`: include a `contentState` object in the relay payload for progress events
|
||||
- Add throttling: don't send more than ~1 progress push per 10-15 seconds to stay within APNs budget
|
||||
- Extend `evt` payload sent to relay:
|
||||
```typescript
|
||||
{
|
||||
secret, serverID, eventType, sessionID, title, body,
|
||||
// New field for Live Activity updates:
|
||||
contentState?: {
|
||||
status: "working" | "retry" | "permission" | "complete" | "error",
|
||||
sessionTitle: string,
|
||||
lastMessage: string,
|
||||
retryInfo: string | null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Push-to-Start
|
||||
|
||||
This lets the server start a Live Activity on the phone when a session begins, even if the user didn't initiate it from the app.
|
||||
|
||||
### 2a. Register push-to-start token from the app
|
||||
|
||||
**File**: `src/hooks/use-live-activity.ts`
|
||||
|
||||
- On app launch, call `addPushToStartTokenListener()` from `expo-widgets`
|
||||
- Send the push-to-start token to the relay at registration time (extend existing `/v1/device/register` or new field)
|
||||
- This token is app-wide (not per-activity), so it lives alongside the device push token
|
||||
|
||||
### 2b. Extend relay for push-to-start
|
||||
|
||||
**Package**: `apn-relay`
|
||||
|
||||
- Add `push_to_start_token` column to `device_registration` table (nullable)
|
||||
- Extend `/v1/device/register` to accept `pushToStartToken` field
|
||||
- New logic in `/v1/event`: if `eventType` is the first event for a session and no `activity_registration` exists yet, send a push-to-start payload:
|
||||
```json
|
||||
{
|
||||
"aps": {
|
||||
"timestamp": 1712345678,
|
||||
"event": "start",
|
||||
"content-state": {
|
||||
"status": "working",
|
||||
"sessionTitle": "Fix auth bug",
|
||||
"lastMessage": "Starting...",
|
||||
"retryInfo": null
|
||||
},
|
||||
"attributes-type": "SessionActivityAttributes",
|
||||
"attributes": {
|
||||
"sessionId": "abc123"
|
||||
},
|
||||
"alert": {
|
||||
"title": "Session Started",
|
||||
"body": "OpenCode is working on: Fix auth bug"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2c. Server-side: emit session start events
|
||||
|
||||
**File**: `packages/opencode/src/server/push-relay.ts`
|
||||
|
||||
- Add a `"start"` event type
|
||||
- Map `session.status` with `type: "busy"` (first time for a session) to `{ type: "start", sessionID }`
|
||||
- Include session metadata (title, directory) in the payload so the relay can populate the `attributes` field for push-to-start
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Polish and Edge Cases
|
||||
|
||||
- **Deep linking**: When user taps the Live Activity, open the app and navigate to that session (`mobilevoice://session/{id}`)
|
||||
- **Multiple activities**: Handle the case where the user starts multiple sessions from different servers. iOS supports multiple concurrent Live Activities.
|
||||
- **Activity expiry**: iOS ends Live Activities after 8 hours. Handle the timeout gracefully (end with a "timed out" status).
|
||||
- **Token rotation**: Activity push tokens can rotate. The `addPushTokenListener` handles this -- forward new tokens to the relay.
|
||||
- **Cleanup**: When the relay receives an APNs error like `InvalidToken` for an activity token, delete the `activity_registration` row.
|
||||
- **Stale activities**: On app foreground, check `SessionActivity.getInstances()` to clean up any orphaned activities.
|
||||
|
||||
---
|
||||
|
||||
## Changes Per Package Summary
|
||||
|
||||
| Package | Files Changed | Files Added |
|
||||
| ---------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------- |
|
||||
| **mobile-voice** | `app.json`, `package.json`, `use-monitoring.ts`, `relay-client.ts` | `src/widgets/session-activity.tsx`, `src/hooks/use-live-activity.ts` |
|
||||
| **apn-relay** | `src/index.ts`, `src/apns.ts`, `src/schema.sql.ts` | (none) |
|
||||
| **opencode** | `src/server/push-relay.ts` | (none) |
|
||||
|
||||
## Build Requirements
|
||||
|
||||
- New EAS dev build required after Phase 1a (native widget extension target)
|
||||
- Relay deployment after Phase 1f
|
||||
- OpenCode server rebuild after Phase 1g
|
||||
|
||||
## Key Technical References
|
||||
|
||||
- `expo-widgets` docs: https://docs.expo.dev/versions/latest/sdk/widgets/
|
||||
- `expo-widgets` alpha blog post: https://expo.dev/blog/home-screen-widgets-and-live-activities-in-expo
|
||||
- Apple ActivityKit push notifications: https://developer.apple.com/documentation/activitykit/starting-and-updating-live-activities-with-activitykit-push-notifications
|
||||
- Existing APN relay: `packages/apn-relay/src/`
|
||||
- Existing push-relay (server-side): `packages/opencode/src/server/push-relay.ts`
|
||||
- Existing monitoring hook: `packages/mobile-voice/src/hooks/use-monitoring.ts`
|
||||
- Existing relay client: `packages/mobile-voice/src/lib/relay-client.ts`
|
||||
|
||||
## Limitations / Risks
|
||||
|
||||
- **expo-widgets is alpha** (March 2026) -- APIs may break
|
||||
- **Images not yet supported** in `@expo/ui` widget components (on Expo's roadmap)
|
||||
- **Live Activities have an 8-hour max duration** enforced by iOS
|
||||
- **APNs budget**: iOS throttles frequent updates; keep progress pushes to ~1 per 10-15 seconds
|
||||
- **NSSupportsLiveActivitiesFrequentUpdates** needed in Info.plist for higher update frequency
|
||||
- **Dev builds required** -- adding the widget extension is a native change, OTA won't cover it
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"cli": {
|
||||
"version": ">= 18.4.0",
|
||||
"appVersionSource": "remote"
|
||||
},
|
||||
"build": {
|
||||
"development": {
|
||||
"bun": "1.3.11",
|
||||
"developmentClient": true,
|
||||
"distribution": "internal",
|
||||
"channel": "development"
|
||||
},
|
||||
"preview": {
|
||||
"bun": "1.3.11",
|
||||
"distribution": "internal",
|
||||
"channel": "preview"
|
||||
},
|
||||
"production": {
|
||||
"bun": "1.3.11",
|
||||
"autoIncrement": true,
|
||||
"channel": "production"
|
||||
}
|
||||
},
|
||||
"submit": {
|
||||
"production": {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// https://docs.expo.dev/guides/using-eslint/
|
||||
const { defineConfig } = require("eslint/config")
|
||||
const tsGuard = require("@typescript-eslint/eslint-plugin")
|
||||
const expoConfig = require("eslint-config-expo/flat")
|
||||
const reactHooksNext = require("eslint-plugin-react-hooks")
|
||||
|
||||
module.exports = defineConfig([
|
||||
expoConfig,
|
||||
{
|
||||
ignores: ["dist/*"],
|
||||
},
|
||||
{
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
"react-hooks-next": reactHooksNext,
|
||||
"ts-guard": tsGuard,
|
||||
},
|
||||
rules: {
|
||||
"ts-guard/no-explicit-any": "warn",
|
||||
"ts-guard/no-floating-promises": "warn",
|
||||
complexity: ["warn", 20],
|
||||
"max-lines": [
|
||||
"warn",
|
||||
{
|
||||
max: 1200,
|
||||
skipBlankLines: true,
|
||||
skipComments: true,
|
||||
},
|
||||
],
|
||||
"max-lines-per-function": [
|
||||
"warn",
|
||||
{
|
||||
max: 250,
|
||||
skipBlankLines: true,
|
||||
skipComments: true,
|
||||
},
|
||||
],
|
||||
"no-console": ["warn", { allow: ["warn", "error"] }],
|
||||
"no-nested-ternary": "warn",
|
||||
"react-hooks/exhaustive-deps": "error",
|
||||
"react-hooks-next/refs": "warn",
|
||||
"react-hooks-next/set-state-in-effect": "warn",
|
||||
"react-hooks-next/static-components": "warn",
|
||||
},
|
||||
},
|
||||
])
|
||||
@@ -0,0 +1,8 @@
|
||||
const { getDefaultConfig } = require("expo/metro-config")
|
||||
|
||||
const config = getDefaultConfig(__dirname)
|
||||
|
||||
// Required for react-native-executorch model files
|
||||
config.resolver.assetExts.push("pte", "bin")
|
||||
|
||||
module.exports = config
|
||||
@@ -0,0 +1,7 @@
|
||||
- While the model is loading for the first time, there should be some fun little like onboarding sequence that you can go through that makes sure the model is automated properly.
|
||||
- When a permission/session complete notification is sent, if you click on it, the session/server should auto be selected.
|
||||
- We need some sort of permissions UI in the top half of the generation.
|
||||
- Need to figure out a good way to start new sessions.
|
||||
- When an agent returns a generation, we should be able to expand it into a reader mode view.
|
||||
- Work on the live activity widget.
|
||||
- In the OpenCode Control app, if a link is generated in Markdown, it should be tappable and open in the device's default browser.
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "mobile-voice",
|
||||
"main": "expo-router/entry",
|
||||
"version": "1.0.2",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"expo:start": "ELECTRON_DISABLE_SANDBOX=1 REACT_NATIVE_PACKAGER_HOSTNAME=exos.husky-tilapia.ts.net expo start --dev-client --clear --host lan",
|
||||
"relay": "echo 'Use packages/apn-relay for APNs relay server'",
|
||||
"relay:legacy": "node ./relay/opencode-relay.mjs",
|
||||
"reset-project": "node ./scripts/reset-project.js",
|
||||
"android": "expo run:android",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web",
|
||||
"lint": "expo lint",
|
||||
"typecheck": "tsc -p tsconfig.typecheck.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fugood/react-native-audio-pcm-stream": "1.1.4",
|
||||
"@react-navigation/bottom-tabs": "^7.15.5",
|
||||
"@react-navigation/elements": "^2.9.10",
|
||||
"@react-navigation/native": "^7.1.33",
|
||||
"expo": "~55.0.9",
|
||||
"expo-asset": "~55.0.10",
|
||||
"expo-audio": "~55.0.9",
|
||||
"expo-camera": "~55.0.11",
|
||||
"expo-constants": "~55.0.9",
|
||||
"expo-dev-client": "~55.0.19",
|
||||
"expo-device": "~55.0.10",
|
||||
"expo-file-system": "~55.0.12",
|
||||
"expo-font": "~55.0.4",
|
||||
"expo-glass-effect": "~55.0.8",
|
||||
"expo-haptics": "~55.0.9",
|
||||
"expo-image": "~55.0.6",
|
||||
"expo-linking": "~55.0.9",
|
||||
"expo-notifications": "~55.0.14",
|
||||
"expo-router": "~55.0.8",
|
||||
"expo-splash-screen": "~55.0.13",
|
||||
"expo-status-bar": "~55.0.4",
|
||||
"expo-symbols": "~55.0.5",
|
||||
"expo-system-ui": "~55.0.11",
|
||||
"expo-task-manager": "~55.0.10",
|
||||
"expo-updates": "~55.0.16",
|
||||
"expo-web-browser": "~55.0.10",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"react-native": "0.83.4",
|
||||
"react-native-audio-api": "^0.11.7",
|
||||
"react-native-gesture-handler": "~2.30.0",
|
||||
"react-native-reanimated": "4.2.1",
|
||||
"react-native-safe-area-context": "~5.6.2",
|
||||
"react-native-screens": "~4.23.0",
|
||||
"react-native-web": "~0.21.0",
|
||||
"react-native-worklets": "0.7.2",
|
||||
"react-native-zeroconf": "0.14.0",
|
||||
"whisper.rn": "0.5.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^8.57.2",
|
||||
"@typescript-eslint/parser": "^8.57.2",
|
||||
"@types/react": "~19.2.2",
|
||||
"babel-preset-expo": "~55.0.8",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"typescript": "~5.9.2"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
# Mobile Voice Refactor Plan
|
||||
|
||||
## Goals
|
||||
|
||||
- Reduce the surface area of `src/app/index.tsx` without changing product behavior.
|
||||
- Make device, network, and monitoring flows easier to reason about.
|
||||
- Move toward React Native / Expo best practices for state, effects, and file structure.
|
||||
- Use the new lint warnings as refactor prompts, not as permanent background noise.
|
||||
|
||||
## Current Pain Points
|
||||
|
||||
- `DictationScreen` currently owns onboarding, permissions, Whisper/model lifecycle, dictation, pairing, server/session sync, relay registration, notification handling, and most UI rendering.
|
||||
- The screen mixes render-time derived state, imperative refs, polling, persistence, and native cleanup in one place.
|
||||
- There are many nested conditionals and long derived blocks that are hard to scan.
|
||||
- Best-effort async cleanup and silent catches make failures harder to understand.
|
||||
|
||||
## Target Shape
|
||||
|
||||
- `src/app/index.tsx`
|
||||
- compose hooks and presentational sections
|
||||
- keep only screen-level orchestration
|
||||
- `src/features/onboarding/`
|
||||
- onboarding step config
|
||||
- onboarding UI component
|
||||
- `src/features/dictation/`
|
||||
- `use-whisper-dictation`
|
||||
- transcript helpers
|
||||
- `src/features/servers/`
|
||||
- server/session refresh and pairing helpers
|
||||
- persisted server state helpers
|
||||
- `src/features/monitoring/`
|
||||
- foreground SSE monitoring
|
||||
- notification payload handling
|
||||
- relay registration helpers
|
||||
- `src/lib/`
|
||||
- parser/validation helpers
|
||||
- logger helper for dev-only diagnostics
|
||||
|
||||
## Refactor Order
|
||||
|
||||
### Phase 1: Extract pure helpers first
|
||||
|
||||
- Move onboarding step text/style selection into a config object or array.
|
||||
- Move server/session payload parsing into dedicated helpers.
|
||||
- Keep existing behavior and props the same.
|
||||
|
||||
### Phase 2: Extract onboarding UI
|
||||
|
||||
- Create an `OnboardingFlow` component that receives explicit state and handlers.
|
||||
- Keep onboarding persistence in the screen until the UI extraction is stable.
|
||||
|
||||
### Phase 3: Extract dictation logic
|
||||
|
||||
- Move Whisper loading, recording, bulk/realtime transcription, and waveform state into a `useWhisperDictation` hook.
|
||||
- Expose a small interface: recording state, transcript, actions, and model status.
|
||||
|
||||
### Phase 4: Extract server/session management
|
||||
|
||||
- Move server restore/save, pairing, health refresh, and active server/session selection into a dedicated hook.
|
||||
- Centralize server parsing and dedupe logic.
|
||||
|
||||
### Phase 5: Extract monitoring and notifications
|
||||
|
||||
- Move SSE monitoring, push payload handling, and relay registration into a `useMonitoring` hook.
|
||||
- Keep side effects close to the feature that owns them.
|
||||
|
||||
### Phase 6: Lint burn-down
|
||||
|
||||
- Replace `any` with explicit parsed shapes.
|
||||
- Reduce nested ternaries in favor of config tables.
|
||||
- Replace ad hoc `console.log` calls with a logger helper or `__DEV__`-gated diagnostics.
|
||||
- Audit bare `.catch(() => {})` and convert non-trivial cases to explicit best-effort helpers or real error handling.
|
||||
|
||||
## Guardrails During Refactor
|
||||
|
||||
- Keep one behavior-preserving slice per PR.
|
||||
- Do not introduce more derived state in `useEffect`.
|
||||
- Prefer explicit hook inputs/outputs over hidden cross-hook coupling.
|
||||
- Only use refs for imperative APIs, subscriptions, and race control.
|
||||
- Re-run lint after each slice.
|
||||
- Validate app behavior in the dev client for microphone, notifications, pairing, and monitoring flows.
|
||||
|
||||
## Exit Criteria
|
||||
|
||||
- `src/app/index.tsx` is mostly screen composition and stays under roughly 800-1200 lines.
|
||||
- Feature logic lives in focused hooks/components with clearer ownership.
|
||||
- New payload parsing does not rely on `any`.
|
||||
- Lint warnings trend down instead of growing.
|
||||
@@ -0,0 +1,328 @@
|
||||
import { createServer } from 'node:http';
|
||||
|
||||
const PORT = Number(process.env.PORT || 8787);
|
||||
const HOST = process.env.HOST || '0.0.0.0';
|
||||
const EXPO_PUSH_URL = 'https://exp.host/--/api/v2/push/send';
|
||||
|
||||
/** @type {Map<string, {jobID: string, sessionID: string, opencodeBaseURL: string, relayBaseURL: string, expoPushToken: string, createdAt: number, done: boolean}>} */
|
||||
const jobs = new Map();
|
||||
|
||||
/** @type {Map<string, {key: string, opencodeBaseURL: string, abortController: AbortController, sessions: Set<string>, running: boolean}>} */
|
||||
const streams = new Map();
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const dedupe = new Set();
|
||||
|
||||
function json(res, status, body) {
|
||||
const value = JSON.stringify(body);
|
||||
res.writeHead(status, {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(value),
|
||||
});
|
||||
res.end(value);
|
||||
}
|
||||
|
||||
async function readJSON(req) {
|
||||
let raw = '';
|
||||
for await (const chunk of req) {
|
||||
raw += chunk;
|
||||
if (raw.length > 1_000_000) {
|
||||
throw new Error('Payload too large');
|
||||
}
|
||||
}
|
||||
if (!raw.trim()) return {};
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
|
||||
function extractSessionID(event) {
|
||||
const properties = event?.properties ?? {};
|
||||
if (typeof properties.sessionID === 'string') return properties.sessionID;
|
||||
if (properties.info && typeof properties.info === 'object' && typeof properties.info.sessionID === 'string') {
|
||||
return properties.info.sessionID;
|
||||
}
|
||||
if (properties.part && typeof properties.part === 'object' && typeof properties.part.sessionID === 'string') {
|
||||
return properties.part.sessionID;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function classifyEvent(event) {
|
||||
const type = String(event?.type || '');
|
||||
const lower = type.toLowerCase();
|
||||
|
||||
if (lower.includes('permission')) return 'permission';
|
||||
if (lower.includes('error')) return 'error';
|
||||
|
||||
if (type === 'session.status') {
|
||||
const statusType = event?.properties?.status?.type;
|
||||
if (statusType === 'idle') return 'complete';
|
||||
}
|
||||
|
||||
if (type === 'message.updated') {
|
||||
const info = event?.properties?.info;
|
||||
if (info && typeof info === 'object') {
|
||||
if (info.error) return 'error';
|
||||
if (info.role === 'assistant' && info.time && typeof info.time === 'object' && info.time.completed) {
|
||||
return 'complete';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function notificationBody(eventType) {
|
||||
if (eventType === 'complete') {
|
||||
return {
|
||||
title: 'Session complete',
|
||||
body: 'OpenCode finished your monitored prompt.',
|
||||
};
|
||||
}
|
||||
if (eventType === 'permission') {
|
||||
return {
|
||||
title: 'Action needed',
|
||||
body: 'OpenCode needs a permission decision.',
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: 'Session error',
|
||||
body: 'OpenCode reported an error for your monitored session.',
|
||||
};
|
||||
}
|
||||
|
||||
async function sendPush({ expoPushToken, eventType, sessionID, jobID }) {
|
||||
const dedupeKey = `${jobID}:${eventType}`;
|
||||
if (dedupe.has(dedupeKey)) return;
|
||||
dedupe.add(dedupeKey);
|
||||
|
||||
const text = notificationBody(eventType);
|
||||
|
||||
const payload = {
|
||||
to: expoPushToken,
|
||||
priority: 'high',
|
||||
_contentAvailable: true,
|
||||
data: {
|
||||
eventType,
|
||||
sessionID,
|
||||
jobID,
|
||||
title: text.title,
|
||||
body: text.body,
|
||||
dedupeKey,
|
||||
at: Date.now(),
|
||||
},
|
||||
};
|
||||
|
||||
const response = await fetch(EXPO_PUSH_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const body = await response.text();
|
||||
throw new Error(`Push send failed (${response.status}): ${body || response.statusText}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function* parseSSE(readable) {
|
||||
const reader = readable.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let pending = '';
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const next = await reader.read();
|
||||
if (next.done) break;
|
||||
|
||||
pending += decoder.decode(next.value, { stream: true });
|
||||
const blocks = pending.split(/\r?\n\r?\n/);
|
||||
pending = blocks.pop() || '';
|
||||
|
||||
for (const block of blocks) {
|
||||
const lines = block.split(/\r?\n/);
|
||||
const dataLines = [];
|
||||
for (const line of lines) {
|
||||
if (!line || line.startsWith(':')) continue;
|
||||
if (line.startsWith('data:')) {
|
||||
dataLines.push(line.slice(5).trimStart());
|
||||
}
|
||||
}
|
||||
if (dataLines.length > 0) {
|
||||
yield dataLines.join('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupStreamIfUnused(baseURL) {
|
||||
const key = baseURL.replace(/\/+$/, '');
|
||||
const entry = streams.get(key);
|
||||
if (!entry) return;
|
||||
|
||||
const stillUsed = Array.from(jobs.values()).some((job) => !job.done && job.opencodeBaseURL === key);
|
||||
if (stillUsed) return;
|
||||
|
||||
entry.abortController.abort();
|
||||
streams.delete(key);
|
||||
}
|
||||
|
||||
async function runStream(baseURL) {
|
||||
const key = baseURL.replace(/\/+$/, '');
|
||||
if (streams.has(key)) return;
|
||||
|
||||
const abortController = new AbortController();
|
||||
streams.set(key, {
|
||||
key,
|
||||
opencodeBaseURL: key,
|
||||
abortController,
|
||||
sessions: new Set(),
|
||||
running: true,
|
||||
});
|
||||
|
||||
while (!abortController.signal.aborted) {
|
||||
try {
|
||||
const response = await fetch(`${key}/event`, {
|
||||
signal: abortController.signal,
|
||||
headers: {
|
||||
Accept: 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok || !response.body) {
|
||||
throw new Error(`SSE connect failed (${response.status})`);
|
||||
}
|
||||
|
||||
for await (const data of parseSSE(response.body)) {
|
||||
if (abortController.signal.aborted) break;
|
||||
|
||||
let event;
|
||||
try {
|
||||
event = JSON.parse(data);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
const sessionID = extractSessionID(event);
|
||||
if (!sessionID) continue;
|
||||
|
||||
const eventType = classifyEvent(event);
|
||||
if (!eventType) continue;
|
||||
|
||||
const related = Array.from(jobs.values()).filter(
|
||||
(job) => !job.done && job.opencodeBaseURL === key && job.sessionID === sessionID,
|
||||
);
|
||||
if (related.length === 0) continue;
|
||||
|
||||
await Promise.allSettled(
|
||||
related.map(async (job) => {
|
||||
await sendPush({
|
||||
expoPushToken: job.expoPushToken,
|
||||
eventType,
|
||||
sessionID,
|
||||
jobID: job.jobID,
|
||||
});
|
||||
|
||||
if (eventType === 'complete' || eventType === 'error') {
|
||||
const current = jobs.get(job.jobID);
|
||||
if (current) current.done = true;
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (abortController.signal.aborted) break;
|
||||
console.warn('[relay] SSE loop error:', error instanceof Error ? error.message : String(error));
|
||||
await new Promise((resolve) => setTimeout(resolve, 1200));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const server = createServer(async (req, res) => {
|
||||
if (!req.url || !req.method) {
|
||||
json(res, 400, { ok: false, error: 'Invalid request' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.url === '/health' && req.method === 'GET') {
|
||||
json(res, 200, {
|
||||
ok: true,
|
||||
activeJobs: Array.from(jobs.values()).filter((job) => !job.done).length,
|
||||
streams: streams.size,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.url === '/v1/monitor/start' && req.method === 'POST') {
|
||||
try {
|
||||
const body = await readJSON(req);
|
||||
const jobID = String(body.jobID || '').trim();
|
||||
const sessionID = String(body.sessionID || '').trim();
|
||||
const opencodeBaseURL = String(body.opencodeBaseURL || '').trim().replace(/\/+$/, '');
|
||||
const relayBaseURL = String(body.relayBaseURL || '').trim().replace(/\/+$/, '');
|
||||
const expoPushToken = String(body.expoPushToken || '').trim();
|
||||
|
||||
if (!jobID || !sessionID || !opencodeBaseURL || !expoPushToken) {
|
||||
json(res, 400, { ok: false, error: 'Missing required fields' });
|
||||
return;
|
||||
}
|
||||
|
||||
jobs.set(jobID, {
|
||||
jobID,
|
||||
sessionID,
|
||||
opencodeBaseURL,
|
||||
relayBaseURL,
|
||||
expoPushToken,
|
||||
createdAt: Date.now(),
|
||||
done: false,
|
||||
});
|
||||
|
||||
runStream(opencodeBaseURL).catch((error) => {
|
||||
console.warn('[relay] runStream failed:', error instanceof Error ? error.message : String(error));
|
||||
});
|
||||
|
||||
json(res, 200, { ok: true });
|
||||
return;
|
||||
} catch (error) {
|
||||
json(res, 500, { ok: false, error: error instanceof Error ? error.message : String(error) });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (req.url === '/v1/monitor/stop' && req.method === 'POST') {
|
||||
try {
|
||||
const body = await readJSON(req);
|
||||
const jobID = String(body.jobID || '').trim();
|
||||
const token = String(body.expoPushToken || '').trim();
|
||||
|
||||
if (!jobID || !token) {
|
||||
json(res, 400, { ok: false, error: 'Missing required fields' });
|
||||
return;
|
||||
}
|
||||
|
||||
const job = jobs.get(jobID);
|
||||
if (job && job.expoPushToken === token) {
|
||||
job.done = true;
|
||||
cleanupStreamIfUnused(job.opencodeBaseURL);
|
||||
}
|
||||
|
||||
json(res, 200, { ok: true });
|
||||
return;
|
||||
} catch (error) {
|
||||
json(res, 500, { ok: false, error: error instanceof Error ? error.message : String(error) });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
json(res, 404, { ok: false, error: 'Not found' });
|
||||
});
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
console.log(`[relay] listening on http://${HOST}:${PORT}`);
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* This script is used to reset the project to a blank state.
|
||||
* It deletes or moves the /src and /scripts directories to /example based on user input and creates a new /src/app directory with an index.tsx and _layout.tsx file.
|
||||
* You can remove the `reset-project` script from package.json and safely delete this file after running it.
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const readline = require("readline");
|
||||
|
||||
const root = process.cwd();
|
||||
const oldDirs = ["src", "scripts"];
|
||||
const exampleDir = "example";
|
||||
const newAppDir = "src/app";
|
||||
const exampleDirPath = path.join(root, exampleDir);
|
||||
|
||||
const indexContent = `import { Text, View, StyleSheet } from "react-native";
|
||||
|
||||
export default function Index() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text>Edit src/app/index.tsx to edit this screen.</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
});
|
||||
`;
|
||||
|
||||
const layoutContent = `import { Stack } from "expo-router";
|
||||
|
||||
export default function RootLayout() {
|
||||
return <Stack />;
|
||||
}
|
||||
`;
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const moveDirectories = async (userInput) => {
|
||||
try {
|
||||
if (userInput === "y") {
|
||||
// Create the app-example directory
|
||||
await fs.promises.mkdir(exampleDirPath, { recursive: true });
|
||||
console.log(`📁 /${exampleDir} directory created.`);
|
||||
}
|
||||
|
||||
// Move old directories to new app-example directory or delete them
|
||||
for (const dir of oldDirs) {
|
||||
const oldDirPath = path.join(root, dir);
|
||||
if (fs.existsSync(oldDirPath)) {
|
||||
if (userInput === "y") {
|
||||
const newDirPath = path.join(root, exampleDir, dir);
|
||||
await fs.promises.rename(oldDirPath, newDirPath);
|
||||
console.log(`➡️ /${dir} moved to /${exampleDir}/${dir}.`);
|
||||
} else {
|
||||
await fs.promises.rm(oldDirPath, { recursive: true, force: true });
|
||||
console.log(`❌ /${dir} deleted.`);
|
||||
}
|
||||
} else {
|
||||
console.log(`➡️ /${dir} does not exist, skipping.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Create new /src/app directory
|
||||
const newAppDirPath = path.join(root, newAppDir);
|
||||
await fs.promises.mkdir(newAppDirPath, { recursive: true });
|
||||
console.log("\n📁 New /src/app directory created.");
|
||||
|
||||
// Create index.tsx
|
||||
const indexPath = path.join(newAppDirPath, "index.tsx");
|
||||
await fs.promises.writeFile(indexPath, indexContent);
|
||||
console.log("📄 src/app/index.tsx created.");
|
||||
|
||||
// Create _layout.tsx
|
||||
const layoutPath = path.join(newAppDirPath, "_layout.tsx");
|
||||
await fs.promises.writeFile(layoutPath, layoutContent);
|
||||
console.log("📄 src/app/_layout.tsx created.");
|
||||
|
||||
console.log("\n✅ Project reset complete. Next steps:");
|
||||
console.log(
|
||||
`1. Run \`npx expo start\` to start a development server.\n2. Edit src/app/index.tsx to edit the main screen.\n3. Put all your application code in /src, only screens and layout files should be in /src/app.${
|
||||
userInput === "y"
|
||||
? `\n4. Delete the /${exampleDir} directory when you're done referencing it.`
|
||||
: ""
|
||||
}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`❌ Error during script execution: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
rl.question(
|
||||
"Do you want to move existing files to /example instead of deleting them? (Y/n): ",
|
||||
(answer) => {
|
||||
const userInput = answer.trim().toLowerCase() || "y";
|
||||
if (userInput === "y" || userInput === "n") {
|
||||
moveDirectories(userInput).finally(() => rl.close());
|
||||
} else {
|
||||
console.log("❌ Invalid input. Please enter 'Y' or 'N'.");
|
||||
rl.close();
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from "react"
|
||||
import { Slot } from "expo-router"
|
||||
import { LogBox } from "react-native"
|
||||
import {
|
||||
configureNotificationBehavior,
|
||||
registerBackgroundNotificationTask,
|
||||
} from "@/notifications/monitoring-notifications"
|
||||
|
||||
// Suppress known non-actionable warnings from third-party libs.
|
||||
LogBox.ignoreLogs([
|
||||
"RecordingNotificationManager is not implemented on iOS",
|
||||
"`transcribeRealtime` is deprecated, use `RealtimeTranscriber` instead",
|
||||
"Parsed error meta:",
|
||||
"Session activation failed",
|
||||
])
|
||||
|
||||
configureNotificationBehavior()
|
||||
registerBackgroundNotificationTask().catch(() => {})
|
||||
|
||||
export default function RootLayout() {
|
||||
return <Slot />
|
||||
}
|
||||
@@ -0,0 +1,5503 @@
|
||||
import React, { useEffect, useState, useRef, useCallback, useMemo } from "react"
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
FlatList,
|
||||
Modal,
|
||||
Alert,
|
||||
ActivityIndicator,
|
||||
LayoutChangeEvent,
|
||||
Linking,
|
||||
Platform,
|
||||
Switch,
|
||||
} from "react-native"
|
||||
import Animated, {
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
withSpring,
|
||||
withSequence,
|
||||
withTiming,
|
||||
Easing,
|
||||
runOnJS,
|
||||
interpolate,
|
||||
Extrapolation,
|
||||
} from "react-native-reanimated"
|
||||
import { SafeAreaView, useSafeAreaInsets } from "react-native-safe-area-context"
|
||||
import { StatusBar } from "expo-status-bar"
|
||||
import { SymbolView } from "expo-symbols"
|
||||
import * as Haptics from "expo-haptics"
|
||||
import { useAudioPlayer } from "expo-audio"
|
||||
import { initWhisper, releaseAllWhisper, type WhisperContext } from "whisper.rn"
|
||||
import { RealtimeTranscriber, type RealtimeTranscribeEvent } from "whisper.rn/src/realtime-transcription"
|
||||
import { AudioPcmStreamAdapter } from "whisper.rn/src/realtime-transcription/adapters/AudioPcmStreamAdapter"
|
||||
import { AudioManager } from "react-native-audio-api"
|
||||
import * as FileSystem from "expo-file-system/legacy"
|
||||
import { fetch as expoFetch } from "expo/fetch"
|
||||
import { buildPermissionCardModel } from "@/lib/pending-permissions"
|
||||
import { unregisterRelayDevice } from "@/lib/relay-client"
|
||||
import { useMdnsDiscovery } from "@/hooks/use-mdns-discovery"
|
||||
import {
|
||||
useMonitoring,
|
||||
type MonitorJob,
|
||||
type PermissionDecision,
|
||||
type PromptHistoryEntry,
|
||||
} from "@/hooks/use-monitoring"
|
||||
import { DEFAULT_RELAY_URL, looksLikeLocalHost, useServerSessions } from "@/hooks/use-server-sessions"
|
||||
import { ensureNotificationPermissions, getDevicePushToken } from "@/notifications/monitoring-notifications"
|
||||
|
||||
const CONTROL_HEIGHT = 86
|
||||
const SEND_SETTLE_MS = 240
|
||||
const WAVEFORM_ROWS = 5
|
||||
const WAVEFORM_CELL_SIZE = 8
|
||||
const WAVEFORM_CELL_GAP = 2
|
||||
const READER_ACTION_SIZE = 20
|
||||
const READER_ACTION_GAP = 14
|
||||
const READER_ACTION_TRAVEL = READER_ACTION_SIZE + READER_ACTION_GAP
|
||||
const READER_ACTION_RAIL_WIDTH = READER_ACTION_SIZE * 2 + READER_ACTION_GAP
|
||||
const AGENT_SUCCESS_GREEN = "#91C29D"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Semantic color tokens
|
||||
// ---------------------------------------------------------------------------
|
||||
const C = {
|
||||
bg: "#121212",
|
||||
surface: "#151515",
|
||||
surfaceRaised: "#17181B",
|
||||
border: "#282828",
|
||||
borderSubtle: "#222733",
|
||||
borderMuted: "#242424",
|
||||
textPrimary: "#F1F1F1",
|
||||
textSecondary: "#D6DAE4",
|
||||
textTertiary: "#B8BDC9",
|
||||
textMuted: "#8F97AA",
|
||||
textDimmed: "#6F7686",
|
||||
textPlaceholder: "#4A5060",
|
||||
blue: "#1D6FF4",
|
||||
blueBorder: "#1557C3",
|
||||
red: "#FF2E3F",
|
||||
redBg: "#421B17",
|
||||
green: AGENT_SUCCESS_GREEN,
|
||||
orange: "#FFB347",
|
||||
yellow: "#D2A542",
|
||||
} as const
|
||||
|
||||
// Unified primary button metrics
|
||||
const BTN_PRIMARY = {
|
||||
minHeight: 52,
|
||||
borderRadius: 16,
|
||||
borderWidth: 2,
|
||||
backgroundColor: C.blue,
|
||||
borderColor: C.blueBorder,
|
||||
} as const
|
||||
const DROPDOWN_VISIBLE_ROWS = 6
|
||||
const DROPDOWN_ROW_HEIGHT = 42
|
||||
const SERVER_MENU_SECTION_HEIGHT = 56
|
||||
const SERVER_MENU_ENTRY_HEIGHT = 36
|
||||
const SERVER_MENU_FOOTER_HEIGHT = 28
|
||||
// If the press duration is shorter than this, treat it as a tap (toggle)
|
||||
const TAP_THRESHOLD_MS = 300
|
||||
const SERVER_STATE_FILE = `${FileSystem.documentDirectory}mobile-voice-servers.json`
|
||||
const WHISPER_SETTINGS_FILE = `${FileSystem.documentDirectory}mobile-voice-whisper-settings.json`
|
||||
const ONBOARDING_STATE_FILE = `${FileSystem.documentDirectory}mobile-voice-onboarding.json`
|
||||
const WHISPER_MODELS_DIR = `${FileSystem.documentDirectory}whisper-models`
|
||||
const WHISPER_REPO = "https://huggingface.co/ggerganov/whisper.cpp/resolve/main"
|
||||
const WHISPER_MODELS = [
|
||||
"ggml-tiny.en-q5_1.bin",
|
||||
"ggml-tiny.en-q8_0.bin",
|
||||
"ggml-tiny.en.bin",
|
||||
"ggml-tiny-q5_1.bin",
|
||||
"ggml-tiny-q8_0.bin",
|
||||
"ggml-tiny.bin",
|
||||
"ggml-base.en-q5_1.bin",
|
||||
"ggml-base.en-q8_0.bin",
|
||||
"ggml-base.en.bin",
|
||||
"ggml-base-q5_1.bin",
|
||||
"ggml-base-q8_0.bin",
|
||||
"ggml-base.bin",
|
||||
"ggml-small.en-q5_1.bin",
|
||||
"ggml-small.en-q8_0.bin",
|
||||
"ggml-small.en.bin",
|
||||
"ggml-small-q5_1.bin",
|
||||
"ggml-small-q8_0.bin",
|
||||
"ggml-small.bin",
|
||||
"ggml-medium.en-q5_0.bin",
|
||||
"ggml-medium.en-q8_0.bin",
|
||||
"ggml-medium.en.bin",
|
||||
"ggml-medium-q5_0.bin",
|
||||
"ggml-medium-q8_0.bin",
|
||||
"ggml-medium.bin",
|
||||
] as const
|
||||
|
||||
type WhisperModelID = (typeof WHISPER_MODELS)[number]
|
||||
type TranscriptionMode = "bulk" | "realtime"
|
||||
type PermissionPromptState = "idle" | "pending" | "granted" | "denied"
|
||||
const DEFAULT_WHISPER_MODEL: WhisperModelID = "ggml-small-q8_0.bin"
|
||||
const DEFAULT_TRANSCRIPTION_MODE: TranscriptionMode = "bulk"
|
||||
|
||||
const WHISPER_MODEL_LABELS: Record<WhisperModelID, string> = {
|
||||
"ggml-tiny.en-q5_1.bin": "tiny.en q5_1",
|
||||
"ggml-tiny.en-q8_0.bin": "tiny.en q8_0",
|
||||
"ggml-tiny.en.bin": "tiny.en",
|
||||
"ggml-tiny-q5_1.bin": "tiny q5_1",
|
||||
"ggml-tiny-q8_0.bin": "tiny q8_0",
|
||||
"ggml-tiny.bin": "tiny",
|
||||
"ggml-base.en-q5_1.bin": "base.en q5_1",
|
||||
"ggml-base.en-q8_0.bin": "base.en q8_0",
|
||||
"ggml-base.en.bin": "base.en",
|
||||
"ggml-base-q5_1.bin": "base q5_1",
|
||||
"ggml-base-q8_0.bin": "base q8_0",
|
||||
"ggml-base.bin": "base",
|
||||
"ggml-small.en-q5_1.bin": "small.en q5_1",
|
||||
"ggml-small.en-q8_0.bin": "small.en q8_0",
|
||||
"ggml-small.en.bin": "small.en",
|
||||
"ggml-small-q5_1.bin": "small q5_1",
|
||||
"ggml-small-q8_0.bin": "small q8_0",
|
||||
"ggml-small.bin": "small",
|
||||
"ggml-medium.en-q5_0.bin": "medium.en q5_0",
|
||||
"ggml-medium.en-q8_0.bin": "medium.en q8_0",
|
||||
"ggml-medium.en.bin": "medium.en",
|
||||
"ggml-medium-q5_0.bin": "medium q5_0",
|
||||
"ggml-medium-q8_0.bin": "medium q8_0",
|
||||
"ggml-medium.bin": "medium",
|
||||
}
|
||||
|
||||
const READER_OPEN_SYMBOL = {
|
||||
ios: "arrow.up.left.and.arrow.down.right",
|
||||
android: "open_in_full",
|
||||
web: "open_in_full",
|
||||
} as const
|
||||
|
||||
const READER_CLOSE_SYMBOL = {
|
||||
ios: "arrow.down.right.and.arrow.up.left",
|
||||
android: "close_fullscreen",
|
||||
web: "close_fullscreen",
|
||||
} as const
|
||||
|
||||
const WHISPER_MODEL_SIZES: Record<WhisperModelID, number> = {
|
||||
"ggml-tiny.en-q5_1.bin": 32166155,
|
||||
"ggml-tiny.en-q8_0.bin": 43550795,
|
||||
"ggml-tiny.en.bin": 77704715,
|
||||
"ggml-tiny-q5_1.bin": 32152673,
|
||||
"ggml-tiny-q8_0.bin": 43537433,
|
||||
"ggml-tiny.bin": 77691713,
|
||||
"ggml-base.en-q5_1.bin": 59721011,
|
||||
"ggml-base.en-q8_0.bin": 81781811,
|
||||
"ggml-base.en.bin": 147964211,
|
||||
"ggml-base-q5_1.bin": 59707625,
|
||||
"ggml-base-q8_0.bin": 81768585,
|
||||
"ggml-base.bin": 147951465,
|
||||
"ggml-small.en-q5_1.bin": 190098681,
|
||||
"ggml-small.en-q8_0.bin": 264477561,
|
||||
"ggml-small.en.bin": 487614201,
|
||||
"ggml-small-q5_1.bin": 190085487,
|
||||
"ggml-small-q8_0.bin": 264464607,
|
||||
"ggml-small.bin": 487601967,
|
||||
"ggml-medium.en-q5_0.bin": 539225533,
|
||||
"ggml-medium.en-q8_0.bin": 823382461,
|
||||
"ggml-medium.en.bin": 1533774781,
|
||||
"ggml-medium-q5_0.bin": 539212467,
|
||||
"ggml-medium-q8_0.bin": 823369779,
|
||||
"ggml-medium.bin": 1533763059,
|
||||
}
|
||||
|
||||
type WhisperModelFamily = {
|
||||
key: string
|
||||
label: string
|
||||
description: string
|
||||
models: WhisperModelID[]
|
||||
}
|
||||
|
||||
const WHISPER_MODEL_FAMILIES: WhisperModelFamily[] = [
|
||||
{
|
||||
key: "tiny",
|
||||
label: "Tiny",
|
||||
description: "Fastest, lowest quality",
|
||||
models: [
|
||||
"ggml-tiny.en-q5_1.bin",
|
||||
"ggml-tiny.en-q8_0.bin",
|
||||
"ggml-tiny.en.bin",
|
||||
"ggml-tiny-q5_1.bin",
|
||||
"ggml-tiny-q8_0.bin",
|
||||
"ggml-tiny.bin",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "base",
|
||||
label: "Base",
|
||||
description: "Good balance of speed and quality",
|
||||
models: [
|
||||
"ggml-base.en-q5_1.bin",
|
||||
"ggml-base.en-q8_0.bin",
|
||||
"ggml-base.en.bin",
|
||||
"ggml-base-q5_1.bin",
|
||||
"ggml-base-q8_0.bin",
|
||||
"ggml-base.bin",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "small",
|
||||
label: "Small",
|
||||
description: "Higher quality, moderate size",
|
||||
models: [
|
||||
"ggml-small.en-q5_1.bin",
|
||||
"ggml-small.en-q8_0.bin",
|
||||
"ggml-small.en.bin",
|
||||
"ggml-small-q5_1.bin",
|
||||
"ggml-small-q8_0.bin",
|
||||
"ggml-small.bin",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "medium",
|
||||
label: "Medium",
|
||||
description: "Best quality, largest download",
|
||||
models: [
|
||||
"ggml-medium.en-q5_0.bin",
|
||||
"ggml-medium.en-q8_0.bin",
|
||||
"ggml-medium.en.bin",
|
||||
"ggml-medium-q5_0.bin",
|
||||
"ggml-medium-q8_0.bin",
|
||||
"ggml-medium.bin",
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
function isWhisperModelID(value: unknown): value is WhisperModelID {
|
||||
return typeof value === "string" && (WHISPER_MODELS as readonly string[]).includes(value)
|
||||
}
|
||||
|
||||
function isEnglishOnlyWhisperModel(modelID: WhisperModelID): boolean {
|
||||
return modelID.includes(".en")
|
||||
}
|
||||
|
||||
function isTranscriptionMode(value: unknown): value is TranscriptionMode {
|
||||
return value === "bulk" || value === "realtime"
|
||||
}
|
||||
|
||||
function formatWhisperModelSize(bytes: number): string {
|
||||
const mib = bytes / (1024 * 1024)
|
||||
if (mib >= 1024) {
|
||||
return `${(mib / 1024).toFixed(1)} GB`
|
||||
}
|
||||
|
||||
return `${Math.round(mib)} MB`
|
||||
}
|
||||
|
||||
function cleanTranscriptText(text: string): string {
|
||||
return text.replace(/[ \t]+$/gm, "").trimEnd()
|
||||
}
|
||||
|
||||
function cleanSessionText(text: string): string {
|
||||
return cleanTranscriptText(text).trimStart()
|
||||
}
|
||||
|
||||
function normalizeTranscriptSessions(text: string): string {
|
||||
const cleaned = cleanTranscriptText(text)
|
||||
if (!cleaned) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return cleaned
|
||||
.split(/\n\n+/)
|
||||
.map((session) => cleanSessionText(session))
|
||||
.filter((session) => session.length > 0)
|
||||
.join("\n\n")
|
||||
}
|
||||
|
||||
function mergeTranscriptChunk(previous: string, chunk: string): string {
|
||||
const cleanPrevious = cleanTranscriptText(previous)
|
||||
const cleanChunk = cleanSessionText(chunk)
|
||||
|
||||
if (!cleanChunk) {
|
||||
return cleanPrevious
|
||||
}
|
||||
|
||||
if (!cleanPrevious) {
|
||||
return cleanChunk
|
||||
}
|
||||
|
||||
const normalizedChunk = cleanChunk
|
||||
if (!normalizedChunk) {
|
||||
return cleanPrevious
|
||||
}
|
||||
|
||||
if (/^[,.;:!?)]/.test(normalizedChunk)) {
|
||||
return `${cleanPrevious}${normalizedChunk}`
|
||||
}
|
||||
|
||||
return `${cleanPrevious} ${normalizedChunk}`
|
||||
}
|
||||
|
||||
function formatSessionUpdated(updatedMs: number): string {
|
||||
if (!updatedMs) return ""
|
||||
|
||||
const now = Date.now()
|
||||
const deltaMs = Math.max(0, now - updatedMs)
|
||||
const deltaMin = Math.floor(deltaMs / 60000)
|
||||
|
||||
if (deltaMin < 60) {
|
||||
return `${Math.max(1, deltaMin)} min`
|
||||
}
|
||||
|
||||
const date = new Date(updatedMs)
|
||||
try {
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
}).format(date)
|
||||
} catch {
|
||||
return date.toLocaleTimeString()
|
||||
}
|
||||
}
|
||||
|
||||
function formatWorkingDirectory(directory?: string): string {
|
||||
if (!directory) return "Not available"
|
||||
|
||||
if (directory.startsWith("/Users/")) {
|
||||
const segments = directory.split("/")
|
||||
if (segments.length >= 4) {
|
||||
const tail = segments.slice(3).join("/")
|
||||
return tail.length > 0 ? `~/${tail}` : "~"
|
||||
}
|
||||
}
|
||||
|
||||
return directory
|
||||
}
|
||||
|
||||
type DropdownMode = "none" | "server" | "session"
|
||||
|
||||
type Pair = {
|
||||
serverID?: string
|
||||
relayURL: string
|
||||
relaySecret: string
|
||||
hosts: string[]
|
||||
}
|
||||
|
||||
type PairHostKind = "tailnet_name" | "tailnet_ip" | "mdns" | "lan" | "loopback" | "public" | "unknown"
|
||||
|
||||
type PairHostOption = {
|
||||
url: string
|
||||
kind: PairHostKind
|
||||
label: string
|
||||
}
|
||||
|
||||
type PairHostProbe = {
|
||||
status: "checking" | "online" | "offline"
|
||||
latencyMs?: number
|
||||
note?: string
|
||||
}
|
||||
|
||||
type ReaderHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6
|
||||
|
||||
type ReaderBlock =
|
||||
| {
|
||||
type: "text"
|
||||
content: string
|
||||
}
|
||||
| {
|
||||
type: "heading"
|
||||
level: ReaderHeadingLevel
|
||||
content: string
|
||||
}
|
||||
| {
|
||||
type: "code"
|
||||
language: string
|
||||
content: string
|
||||
}
|
||||
|
||||
type ReaderInlineSegment =
|
||||
| {
|
||||
type: "text"
|
||||
content: string
|
||||
}
|
||||
| {
|
||||
type: "inline_code"
|
||||
content: string
|
||||
}
|
||||
| {
|
||||
type: "italic"
|
||||
content: string
|
||||
}
|
||||
| {
|
||||
type: "bold"
|
||||
content: string
|
||||
}
|
||||
| {
|
||||
type: "bold_italic"
|
||||
content: string
|
||||
}
|
||||
|
||||
const AUDIO_SESSION_BUSY_MESSAGE = "Microphone is unavailable while another call is active. End the call and try again."
|
||||
|
||||
type Scan = {
|
||||
data: string
|
||||
}
|
||||
|
||||
type WhisperSavedState = {
|
||||
defaultModel: WhisperModelID
|
||||
mode: TranscriptionMode
|
||||
autoSendOnDictationEnd: boolean
|
||||
}
|
||||
|
||||
type OnboardingSavedState = {
|
||||
completed: boolean
|
||||
}
|
||||
|
||||
type Cam = {
|
||||
CameraView: (typeof import("expo-camera"))["CameraView"]
|
||||
requestCameraPermissionsAsync: () => Promise<{ granted: boolean }>
|
||||
}
|
||||
|
||||
function parsePairShape(data: unknown): Pair | undefined {
|
||||
if (!data || typeof data !== "object") return
|
||||
const version = (data as { v?: unknown }).v
|
||||
if (version !== undefined && version !== 1) return
|
||||
if (typeof (data as { relaySecret?: unknown }).relaySecret !== "string") return
|
||||
if (!Array.isArray((data as { hosts?: unknown }).hosts)) return
|
||||
const hosts = (data as { hosts: unknown[] }).hosts.filter((item): item is string => typeof item === "string")
|
||||
if (!hosts.length) return
|
||||
const relayURLRaw = (data as { relayURL?: unknown }).relayURL
|
||||
const relayURL = typeof relayURLRaw === "string" && relayURLRaw.length > 0 ? relayURLRaw : DEFAULT_RELAY_URL
|
||||
const serverIDRaw = (data as { serverID?: unknown }).serverID
|
||||
const serverID = typeof serverIDRaw === "string" && serverIDRaw.length > 0 ? serverIDRaw : undefined
|
||||
return {
|
||||
serverID,
|
||||
relayURL,
|
||||
relaySecret: (data as { relaySecret: string }).relaySecret,
|
||||
hosts,
|
||||
}
|
||||
}
|
||||
|
||||
function parsePair(input: string): Pair | undefined {
|
||||
const raw = input.trim()
|
||||
if (!raw) return
|
||||
|
||||
const candidates: string[] = [raw]
|
||||
|
||||
try {
|
||||
const url = new URL(raw)
|
||||
const query = url.searchParams.get("pair") ?? url.searchParams.get("payload")
|
||||
if (query) {
|
||||
candidates.unshift(query)
|
||||
}
|
||||
} catch {
|
||||
// Raw JSON payload is still supported.
|
||||
}
|
||||
|
||||
const seen = new Set<string>()
|
||||
for (const candidate of candidates) {
|
||||
if (!candidate || seen.has(candidate)) continue
|
||||
seen.add(candidate)
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(candidate)
|
||||
const pair = parsePairShape(parsed)
|
||||
if (pair) {
|
||||
return pair
|
||||
}
|
||||
} catch {
|
||||
// keep trying fallbacks
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isLoopback(hostname: string): boolean {
|
||||
return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "0.0.0.0" || hostname === "::1"
|
||||
}
|
||||
|
||||
function isCarrierGradeNat(hostname: string): boolean {
|
||||
const match = /^100\.(\d{1,3})\./.exec(hostname)
|
||||
if (!match) return false
|
||||
const octet = Number(match[1])
|
||||
return octet >= 64 && octet <= 127
|
||||
}
|
||||
|
||||
function classifyPairHost(hostname: string): PairHostKind {
|
||||
if (isLoopback(hostname)) return "loopback"
|
||||
if (hostname.endsWith(".ts.net")) return "tailnet_name"
|
||||
if (isCarrierGradeNat(hostname)) return "tailnet_ip"
|
||||
if (hostname.endsWith(".local")) return "mdns"
|
||||
if (hostname.startsWith("10.") || hostname.startsWith("192.168.") || /^172\.(1[6-9]|2\d|3[0-1])\./.test(hostname)) {
|
||||
return "lan"
|
||||
}
|
||||
if (hostname.includes(".")) return "public"
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
function pairHostKindLabel(kind: PairHostKind): string {
|
||||
switch (kind) {
|
||||
case "tailnet_name":
|
||||
return "Tailscale DNS"
|
||||
case "tailnet_ip":
|
||||
return "Tailscale IP"
|
||||
case "mdns":
|
||||
return "mDNS"
|
||||
case "lan":
|
||||
return "LAN"
|
||||
case "loopback":
|
||||
return "Loopback"
|
||||
case "public":
|
||||
return "Public"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
function normalizePairHosts(input: string[]): PairHostOption[] {
|
||||
const seen = new Set<string>()
|
||||
const normalized = input
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
.map((item) => {
|
||||
try {
|
||||
const parsed = new URL(item)
|
||||
const url = `${parsed.protocol}//${parsed.host}`
|
||||
if (seen.has(url)) return null
|
||||
seen.add(url)
|
||||
return {
|
||||
url,
|
||||
kind: classifyPairHost(parsed.hostname),
|
||||
label: parsed.hostname,
|
||||
} as PairHostOption
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})
|
||||
.filter((item): item is PairHostOption => !!item)
|
||||
|
||||
const nonLoopback = normalized.filter((item) => item.kind !== "loopback")
|
||||
return nonLoopback.length > 0 ? nonLoopback : normalized
|
||||
}
|
||||
|
||||
function pairProbeLabel(probe: PairHostProbe | undefined): string {
|
||||
if (!probe || probe.status === "checking") return "Checking..."
|
||||
if (probe.status === "online") return `${probe.latencyMs ?? 0} ms`
|
||||
return probe.note ?? "Unavailable"
|
||||
}
|
||||
|
||||
function pairProbeSummary(probe: PairHostProbe | undefined): string {
|
||||
if (!probe || probe.status === "checking") {
|
||||
return "Health check in progress"
|
||||
}
|
||||
|
||||
if (probe.status === "online") {
|
||||
return `Healthy, reached in ${probe.latencyMs ?? 0} ms`
|
||||
}
|
||||
|
||||
return `Health check: ${probe.note ?? "Unavailable"}`
|
||||
}
|
||||
|
||||
function parseReaderBlocks(input: string): ReaderBlock[] {
|
||||
const normalized = input.replace(/\r\n/g, "\n")
|
||||
const lines = normalized.split("\n")
|
||||
|
||||
const blocks: ReaderBlock[] = []
|
||||
const prose: string[] = []
|
||||
const code: string[] = []
|
||||
let fence: "```" | "~~~" | null = null
|
||||
let language = ""
|
||||
|
||||
const flushProse = () => {
|
||||
const content = prose.join("\n").trim()
|
||||
if (content.length > 0) {
|
||||
blocks.push({ type: "text", content })
|
||||
}
|
||||
prose.length = 0
|
||||
}
|
||||
|
||||
const flushCode = () => {
|
||||
const content = code.join("\n").replace(/\n+$/, "")
|
||||
blocks.push({ type: "code", language, content })
|
||||
code.length = 0
|
||||
language = ""
|
||||
fence = null
|
||||
}
|
||||
|
||||
for (const line of lines) {
|
||||
if (!fence) {
|
||||
const match = /^\s*(```|~~~)(.*)$/.exec(line)
|
||||
if (match) {
|
||||
flushProse()
|
||||
fence = match[1] as "```" | "~~~"
|
||||
language = match[2]?.trim().split(/\s+/)[0] ?? ""
|
||||
} else {
|
||||
const headingMatch = /^\s{0,3}(#{1,6})\s+(.+?)\s*#*\s*$/.exec(line)
|
||||
if (headingMatch) {
|
||||
flushProse()
|
||||
blocks.push({
|
||||
type: "heading",
|
||||
level: headingMatch[1].length as ReaderHeadingLevel,
|
||||
content: headingMatch[2].trim(),
|
||||
})
|
||||
} else {
|
||||
prose.push(line)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (line.trimStart().startsWith(fence)) {
|
||||
flushCode()
|
||||
continue
|
||||
}
|
||||
|
||||
code.push(line)
|
||||
}
|
||||
|
||||
if (fence) {
|
||||
prose.push(`${fence}${language ? language : ""}`)
|
||||
prose.push(...code)
|
||||
}
|
||||
|
||||
flushProse()
|
||||
|
||||
return blocks
|
||||
}
|
||||
|
||||
function parseReaderAsteriskSegments(input: string): ReaderInlineSegment[] {
|
||||
const segments: ReaderInlineSegment[] = []
|
||||
let cursor = 0
|
||||
let textStart = 0
|
||||
|
||||
while (cursor < input.length) {
|
||||
if (input[cursor] !== "*") {
|
||||
cursor += 1
|
||||
continue
|
||||
}
|
||||
|
||||
const marker = input.startsWith("***", cursor) ? "***" : input.startsWith("**", cursor) ? "**" : "*"
|
||||
const end = input.indexOf(marker, cursor + marker.length)
|
||||
if (end === -1) {
|
||||
cursor += 1
|
||||
continue
|
||||
}
|
||||
|
||||
const content = input.slice(cursor + marker.length, end)
|
||||
if (content.trim().length === 0 || content !== content.trim() || content.includes("\n")) {
|
||||
cursor += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if (cursor > textStart) {
|
||||
segments.push({ type: "text", content: input.slice(textStart, cursor) })
|
||||
}
|
||||
|
||||
segments.push({
|
||||
type: marker === "***" ? "bold_italic" : marker === "**" ? "bold" : "italic",
|
||||
content,
|
||||
})
|
||||
|
||||
cursor = end + marker.length
|
||||
textStart = cursor
|
||||
}
|
||||
|
||||
if (textStart < input.length) {
|
||||
segments.push({ type: "text", content: input.slice(textStart) })
|
||||
}
|
||||
|
||||
return segments.length > 0 ? segments : [{ type: "text", content: input }]
|
||||
}
|
||||
|
||||
function parseReaderInlineSegments(input: string): ReaderInlineSegment[] {
|
||||
const segments: ReaderInlineSegment[] = []
|
||||
const pattern = /(`+|~+)([^`~\n]+?)\1/g
|
||||
let cursor = 0
|
||||
|
||||
for (const match of input.matchAll(pattern)) {
|
||||
const full = match[0] ?? ""
|
||||
const code = match[2] ?? ""
|
||||
const start = match.index ?? 0
|
||||
const end = start + full.length
|
||||
|
||||
if (start > cursor) {
|
||||
segments.push(...parseReaderAsteriskSegments(input.slice(cursor, start)))
|
||||
}
|
||||
|
||||
if (code.length > 0) {
|
||||
segments.push({ type: "inline_code", content: code })
|
||||
}
|
||||
|
||||
cursor = end
|
||||
}
|
||||
|
||||
if (cursor < input.length) {
|
||||
segments.push(...parseReaderAsteriskSegments(input.slice(cursor)))
|
||||
}
|
||||
|
||||
if (segments.length === 0) {
|
||||
segments.push({ type: "text", content: input })
|
||||
}
|
||||
|
||||
return segments
|
||||
}
|
||||
|
||||
function isAudioSessionBusyError(error: unknown): boolean {
|
||||
const message = error instanceof Error ? `${error.name} ${error.message}` : String(error ?? "")
|
||||
return (
|
||||
message.includes("InsufficientPriority") ||
|
||||
message.includes("561017449") ||
|
||||
message.includes("Session activation failed")
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeAudioStartErrorMessage(error: unknown): string {
|
||||
if (isAudioSessionBusyError(error)) {
|
||||
return AUDIO_SESSION_BUSY_MESSAGE
|
||||
}
|
||||
|
||||
const raw = error instanceof Error ? error.message.trim() : String(error ?? "").trim()
|
||||
if (!raw) {
|
||||
return "Unable to activate microphone."
|
||||
}
|
||||
|
||||
return raw
|
||||
}
|
||||
|
||||
export default function DictationScreen() {
|
||||
const insets = useSafeAreaInsets()
|
||||
const [camera, setCamera] = useState<Cam | null>(null)
|
||||
const [defaultWhisperModel, setDefaultWhisperModel] = useState<WhisperModelID>(DEFAULT_WHISPER_MODEL)
|
||||
const [onboardingReady, setOnboardingReady] = useState(false)
|
||||
const [onboardingComplete, setOnboardingComplete] = useState(false)
|
||||
const [onboardingStep, setOnboardingStep] = useState(0)
|
||||
const [microphonePermissionState, setMicrophonePermissionState] = useState<PermissionPromptState>("idle")
|
||||
const [notificationPermissionState, setNotificationPermissionState] = useState<PermissionPromptState>("idle")
|
||||
const [localNetworkPermissionState, setLocalNetworkPermissionState] = useState<PermissionPromptState>("idle")
|
||||
const [activeWhisperModel, setActiveWhisperModel] = useState<WhisperModelID | null>(null)
|
||||
const [installedWhisperModels, setInstalledWhisperModels] = useState<WhisperModelID[]>([])
|
||||
const [whisperSettingsOpen, setWhisperSettingsOpen] = useState(false)
|
||||
const [downloadingModelID, setDownloadingModelID] = useState<WhisperModelID | null>(null)
|
||||
const [downloadProgress, setDownloadProgress] = useState(0)
|
||||
const [isPreparingWhisperModel, setIsPreparingWhisperModel] = useState(true)
|
||||
const [transcriptionMode, setTranscriptionMode] = useState<TranscriptionMode>(DEFAULT_TRANSCRIPTION_MODE)
|
||||
const [autoSendOnDictationEnd, setAutoSendOnDictationEnd] = useState(false)
|
||||
const [isTranscribingBulk, setIsTranscribingBulk] = useState(false)
|
||||
const [whisperError, setWhisperError] = useState("")
|
||||
const [transcribedText, setTranscribedText] = useState("")
|
||||
const [isRecording, setIsRecording] = useState(false)
|
||||
const [permissionGranted, setPermissionGranted] = useState(false)
|
||||
const [controlsWidth, setControlsWidth] = useState(0)
|
||||
const [hasCompletedSession, setHasCompletedSession] = useState(false)
|
||||
const [isSending, setIsSending] = useState(false)
|
||||
const [agentStateDismissed, setAgentStateDismissed] = useState(false)
|
||||
const [readerModeOpen, setReaderModeOpen] = useState(false)
|
||||
const [readerModeRendered, setReaderModeRendered] = useState(false)
|
||||
const [transcriptionAreaHeight, setTranscriptionAreaHeight] = useState(0)
|
||||
const [agentStateCardHeight, setAgentStateCardHeight] = useState(0)
|
||||
const [dropdownMode, setDropdownMode] = useState<DropdownMode>("none")
|
||||
const [dropdownRenderMode, setDropdownRenderMode] = useState<Exclude<DropdownMode, "none">>("server")
|
||||
const [serverMenuListHeight, setServerMenuListHeight] = useState(0)
|
||||
const [sessionMenuListHeight, setSessionMenuListHeight] = useState(0)
|
||||
const [serverMenuFooterHeight, setServerMenuFooterHeight] = useState(0)
|
||||
const [sessionMenuFooterHeight, setSessionMenuFooterHeight] = useState(0)
|
||||
const [sessionCreateMode, setSessionCreateMode] = useState<"same" | "root" | null>(null)
|
||||
const [scanOpen, setScanOpen] = useState(false)
|
||||
const [pairSelectionOpen, setPairSelectionOpen] = useState(false)
|
||||
const [pendingPair, setPendingPair] = useState<Pair | null>(null)
|
||||
const [pairHostOptions, setPairHostOptions] = useState<PairHostOption[]>([])
|
||||
const [selectedPairHostURL, setSelectedPairHostURL] = useState<string | null>(null)
|
||||
const [pairHostProbes, setPairHostProbes] = useState<Record<string, PairHostProbe>>({})
|
||||
const [isConnectingPairHost, setIsConnectingPairHost] = useState(false)
|
||||
const [camGranted, setCamGranted] = useState(false)
|
||||
const [waveformLevels, setWaveformLevels] = useState<number[]>(Array.from({ length: 24 }, () => 0))
|
||||
const [waveformTick, setWaveformTick] = useState(0)
|
||||
const waveformLevelsRef = useRef<number[]>(Array.from({ length: 24 }, () => 0))
|
||||
const lastWaveformCommitRef = useRef(0)
|
||||
const sendPlayer = useAudioPlayer(require("../../assets/sounds/send-whoosh.mp3"))
|
||||
const completePlayer = useAudioPlayer(require("../../assets/sounds/complete.wav"))
|
||||
|
||||
const isRecordingRef = useRef(false)
|
||||
const isStartingRef = useRef(false)
|
||||
const activeSessionRef = useRef(0)
|
||||
const scrollViewRef = useRef<ScrollView>(null)
|
||||
const isHoldingRef = useRef(false)
|
||||
const pressInTimeRef = useRef(0)
|
||||
const accumulatedRef = useRef("")
|
||||
const baseTextRef = useRef("")
|
||||
const whisperContextRef = useRef<WhisperContext | null>(null)
|
||||
const whisperContextModelRef = useRef<WhisperModelID | null>(null)
|
||||
const whisperTranscriberRef = useRef<RealtimeTranscriber | null>(null)
|
||||
const bulkAudioStreamRef = useRef<AudioPcmStreamAdapter | null>(null)
|
||||
const bulkAudioChunksRef = useRef<Uint8Array[]>([])
|
||||
const bulkTranscriptionJobRef = useRef(0)
|
||||
const downloadProgressRef = useRef(0)
|
||||
const autoSendSignatureRef = useRef("")
|
||||
const waveformPulseIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
const sendSettleTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||
const scanLockRef = useRef(false)
|
||||
const pairProbeRunRef = useRef(0)
|
||||
const whisperRestoredRef = useRef(false)
|
||||
const promptPagerRef = useRef<FlatList<PromptHistoryEntry | "live">>(null)
|
||||
const promptPagerPageRef = useRef(-1)
|
||||
|
||||
const closeDropdown = useCallback(() => {
|
||||
setDropdownMode("none")
|
||||
}, [])
|
||||
|
||||
const discoveryEnabled = onboardingComplete && localNetworkPermissionState !== "denied" && dropdownMode === "server"
|
||||
|
||||
const {
|
||||
servers,
|
||||
serversRef,
|
||||
activeServerId,
|
||||
setActiveServerId,
|
||||
activeServerIdRef,
|
||||
activeSessionId,
|
||||
setActiveSessionId,
|
||||
activeSessionIdRef,
|
||||
restoredRef,
|
||||
refreshServerStatusAndSessions,
|
||||
refreshAllServerHealth,
|
||||
selectServer,
|
||||
selectSession,
|
||||
removeServer,
|
||||
addServer,
|
||||
createSession,
|
||||
findServerForSession,
|
||||
} = useServerSessions()
|
||||
|
||||
const { discoveredServers, discoveryStatus, discoveryError, discoveryAvailable, refreshDiscovery } = useMdnsDiscovery(
|
||||
{
|
||||
enabled: discoveryEnabled,
|
||||
},
|
||||
)
|
||||
|
||||
const {
|
||||
beginMonitoring,
|
||||
activePermissionRequest,
|
||||
devicePushToken,
|
||||
latestAssistantContext,
|
||||
latestPromptText,
|
||||
latestAssistantResponse,
|
||||
monitorJob,
|
||||
monitorStatus,
|
||||
pendingPermissionCount,
|
||||
promptHistory,
|
||||
respondingPermissionID,
|
||||
respondToPermission,
|
||||
setDevicePushToken,
|
||||
setLatestPromptText,
|
||||
setPromptHistory,
|
||||
setMonitorStatus,
|
||||
} = useMonitoring({
|
||||
completePlayer,
|
||||
closeDropdown,
|
||||
findServerForSession,
|
||||
refreshServerStatusAndSessions,
|
||||
servers,
|
||||
serversRef,
|
||||
restoredRef,
|
||||
activeServerId,
|
||||
activeSessionId,
|
||||
activeServerIdRef,
|
||||
activeSessionIdRef,
|
||||
setActiveServerId,
|
||||
setActiveSessionId,
|
||||
setAgentStateDismissed,
|
||||
setNotificationPermissionState,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true
|
||||
|
||||
void (async () => {
|
||||
let complete = false
|
||||
|
||||
try {
|
||||
const data = await FileSystem.readAsStringAsync(ONBOARDING_STATE_FILE)
|
||||
if (data) {
|
||||
const parsed = JSON.parse(data) as Partial<OnboardingSavedState>
|
||||
complete = Boolean(parsed.completed)
|
||||
}
|
||||
} catch {
|
||||
// No onboarding state file yet.
|
||||
}
|
||||
|
||||
if (!complete) {
|
||||
try {
|
||||
const [serverInfo, whisperInfo] = await Promise.all([
|
||||
FileSystem.getInfoAsync(SERVER_STATE_FILE),
|
||||
FileSystem.getInfoAsync(WHISPER_SETTINGS_FILE),
|
||||
])
|
||||
|
||||
if (serverInfo.exists || whisperInfo.exists) {
|
||||
complete = true
|
||||
}
|
||||
} catch {
|
||||
// Keep first-install behavior if metadata check fails.
|
||||
}
|
||||
|
||||
if (complete) {
|
||||
void FileSystem.writeAsStringAsync(ONBOARDING_STATE_FILE, JSON.stringify({ completed: true })).catch(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setOnboardingComplete(complete)
|
||||
setOnboardingReady(true)
|
||||
}
|
||||
})()
|
||||
|
||||
return () => {
|
||||
mounted = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
const modelPath = useCallback((modelID: WhisperModelID) => `${WHISPER_MODELS_DIR}/${modelID}`, [])
|
||||
|
||||
const refreshInstalledWhisperModels = useCallback(async () => {
|
||||
const next: WhisperModelID[] = []
|
||||
|
||||
for (const modelID of WHISPER_MODELS) {
|
||||
try {
|
||||
const info = await FileSystem.getInfoAsync(modelPath(modelID))
|
||||
if (info.exists) {
|
||||
next.push(modelID)
|
||||
}
|
||||
} catch {
|
||||
// Ignore model metadata read errors.
|
||||
}
|
||||
}
|
||||
|
||||
setInstalledWhisperModels(next)
|
||||
return next
|
||||
}, [modelPath])
|
||||
|
||||
const stopWaveformPulse = useCallback(() => {
|
||||
if (waveformPulseIntervalRef.current) {
|
||||
clearInterval(waveformPulseIntervalRef.current)
|
||||
waveformPulseIntervalRef.current = null
|
||||
}
|
||||
}, [])
|
||||
|
||||
const clearWaveform = useCallback(() => {
|
||||
const cleared = new Array(waveformLevelsRef.current.length).fill(0)
|
||||
waveformLevelsRef.current = cleared
|
||||
setWaveformLevels(cleared)
|
||||
setWaveformTick(Date.now())
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (sendSettleTimeoutRef.current) {
|
||||
clearTimeout(sendSettleTimeoutRef.current)
|
||||
}
|
||||
stopWaveformPulse()
|
||||
}
|
||||
}, [stopWaveformPulse])
|
||||
|
||||
const ensureAudioInputRoute = useCallback(async () => {
|
||||
try {
|
||||
const devices = await AudioManager.getDevicesInfo()
|
||||
if (devices.currentInputs.length === 0 && devices.availableInputs.length > 0) {
|
||||
const pick = devices.availableInputs[0]
|
||||
await AudioManager.setInputDevice(pick.id)
|
||||
}
|
||||
} catch {
|
||||
// Input route setup is best-effort.
|
||||
}
|
||||
}, [])
|
||||
|
||||
const activateAudioSession = useCallback(
|
||||
async (trigger: "startup" | "record") => {
|
||||
try {
|
||||
await AudioManager.setAudioSessionActivity(true)
|
||||
return true
|
||||
} catch (error) {
|
||||
const message = normalizeAudioStartErrorMessage(error)
|
||||
if (trigger === "record") {
|
||||
setWhisperError(message)
|
||||
}
|
||||
|
||||
if (isAudioSessionBusyError(error)) {
|
||||
console.warn("[Audio] Session activation deferred:", { trigger, message })
|
||||
return false
|
||||
}
|
||||
|
||||
console.warn("[Audio] Session activation failed:", { trigger, message })
|
||||
return false
|
||||
}
|
||||
},
|
||||
[setWhisperError],
|
||||
)
|
||||
|
||||
// Set up audio session and check microphone permissions on mount.
|
||||
useEffect(() => {
|
||||
void (async () => {
|
||||
try {
|
||||
AudioManager.setAudioSessionOptions({
|
||||
iosCategory: "playAndRecord",
|
||||
iosMode: "spokenAudio",
|
||||
iosOptions: ["allowBluetoothHFP", "defaultToSpeaker"],
|
||||
})
|
||||
|
||||
const sessionReady = await activateAudioSession("startup")
|
||||
|
||||
const permission = await AudioManager.checkRecordingPermissions()
|
||||
const granted = permission === "Granted"
|
||||
setPermissionGranted(granted)
|
||||
setMicrophonePermissionState(granted ? "granted" : permission === "Denied" ? "denied" : "idle")
|
||||
|
||||
if (granted && sessionReady) {
|
||||
await ensureAudioInputRoute()
|
||||
}
|
||||
} catch (e) {
|
||||
const message = normalizeAudioStartErrorMessage(e)
|
||||
console.warn("[Audio] Setup warning:", message)
|
||||
}
|
||||
})()
|
||||
}, [activateAudioSession, ensureAudioInputRoute])
|
||||
|
||||
const loadWhisperContext = useCallback(
|
||||
async (modelID: WhisperModelID) => {
|
||||
if (whisperContextRef.current && whisperContextModelRef.current === modelID) {
|
||||
setActiveWhisperModel(modelID)
|
||||
return whisperContextRef.current
|
||||
}
|
||||
|
||||
setIsPreparingWhisperModel(true)
|
||||
setWhisperError("")
|
||||
|
||||
try {
|
||||
const existing = whisperContextRef.current
|
||||
whisperContextRef.current = null
|
||||
whisperContextModelRef.current = null
|
||||
if (existing) {
|
||||
await existing.release().catch(() => {})
|
||||
}
|
||||
|
||||
const context = await initWhisper({
|
||||
filePath: modelPath(modelID),
|
||||
useGpu: Platform.OS === "ios",
|
||||
})
|
||||
|
||||
whisperContextRef.current = context
|
||||
whisperContextModelRef.current = modelID
|
||||
setActiveWhisperModel(modelID)
|
||||
return context
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Failed to load Whisper model"
|
||||
setWhisperError(message)
|
||||
throw error
|
||||
} finally {
|
||||
setIsPreparingWhisperModel(false)
|
||||
}
|
||||
},
|
||||
[modelPath],
|
||||
)
|
||||
|
||||
const downloadWhisperModel = useCallback(
|
||||
async (modelID: WhisperModelID) => {
|
||||
if (downloadingModelID && downloadingModelID !== modelID) {
|
||||
return false
|
||||
}
|
||||
|
||||
setDownloadingModelID(modelID)
|
||||
downloadProgressRef.current = 0
|
||||
setDownloadProgress(0)
|
||||
setWhisperError("")
|
||||
|
||||
try {
|
||||
await FileSystem.makeDirectoryAsync(WHISPER_MODELS_DIR, { intermediates: true }).catch(() => {})
|
||||
|
||||
const targetPath = modelPath(modelID)
|
||||
await FileSystem.deleteAsync(targetPath, { idempotent: true }).catch(() => {})
|
||||
|
||||
const download = FileSystem.createDownloadResumable(
|
||||
`${WHISPER_REPO}/${modelID}`,
|
||||
targetPath,
|
||||
{},
|
||||
(event: FileSystem.DownloadProgressData) => {
|
||||
const total = event.totalBytesExpectedToWrite
|
||||
if (!total) return
|
||||
const rawProgress = Math.max(0, Math.min(1, event.totalBytesWritten / total))
|
||||
const progress = Math.max(downloadProgressRef.current, rawProgress)
|
||||
downloadProgressRef.current = progress
|
||||
setDownloadProgress(progress)
|
||||
},
|
||||
)
|
||||
|
||||
const result = await download.downloadAsync()
|
||||
if (!result?.uri) {
|
||||
throw new Error("Whisper model download did not complete")
|
||||
}
|
||||
|
||||
await refreshInstalledWhisperModels()
|
||||
return true
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Failed to download Whisper model"
|
||||
setWhisperError(message)
|
||||
return false
|
||||
} finally {
|
||||
setDownloadingModelID((current) => (current === modelID ? null : current))
|
||||
}
|
||||
},
|
||||
[downloadingModelID, modelPath, refreshInstalledWhisperModels],
|
||||
)
|
||||
|
||||
const ensureWhisperModelReady = useCallback(
|
||||
async (modelID: WhisperModelID) => {
|
||||
const info = await FileSystem.getInfoAsync(modelPath(modelID))
|
||||
if (!info.exists) {
|
||||
const downloaded = await downloadWhisperModel(modelID)
|
||||
if (!downloaded) {
|
||||
throw new Error(`Unable to download ${modelID}`)
|
||||
}
|
||||
}
|
||||
return loadWhisperContext(modelID)
|
||||
},
|
||||
[downloadWhisperModel, loadWhisperContext, modelPath],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true
|
||||
|
||||
void (async () => {
|
||||
await FileSystem.makeDirectoryAsync(WHISPER_MODELS_DIR, { intermediates: true }).catch(() => {})
|
||||
|
||||
let nextDefaultModel: WhisperModelID = DEFAULT_WHISPER_MODEL
|
||||
let nextMode: TranscriptionMode = DEFAULT_TRANSCRIPTION_MODE
|
||||
let nextAutoSendOnDictationEnd = false
|
||||
try {
|
||||
const data = await FileSystem.readAsStringAsync(WHISPER_SETTINGS_FILE)
|
||||
if (data) {
|
||||
const parsed = JSON.parse(data) as Partial<WhisperSavedState>
|
||||
if (isWhisperModelID(parsed.defaultModel)) {
|
||||
nextDefaultModel = parsed.defaultModel
|
||||
}
|
||||
if (isTranscriptionMode(parsed.mode)) {
|
||||
nextMode = parsed.mode
|
||||
}
|
||||
if (parsed.autoSendOnDictationEnd === true) {
|
||||
nextAutoSendOnDictationEnd = true
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Use default settings if state file is missing or invalid.
|
||||
}
|
||||
|
||||
if (!mounted) return
|
||||
|
||||
whisperRestoredRef.current = true
|
||||
setDefaultWhisperModel(nextDefaultModel)
|
||||
setTranscriptionMode(nextMode)
|
||||
setAutoSendOnDictationEnd(nextAutoSendOnDictationEnd)
|
||||
|
||||
await refreshInstalledWhisperModels()
|
||||
|
||||
try {
|
||||
await ensureWhisperModelReady(nextDefaultModel)
|
||||
} catch (error) {
|
||||
console.error("[Whisper] Failed to initialize default model:", error)
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setIsPreparingWhisperModel(false)
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
return () => {
|
||||
mounted = false
|
||||
}
|
||||
}, [ensureWhisperModelReady, refreshInstalledWhisperModels])
|
||||
|
||||
useEffect(() => {
|
||||
if (!whisperRestoredRef.current) return
|
||||
const payload: WhisperSavedState = {
|
||||
defaultModel: defaultWhisperModel,
|
||||
mode: transcriptionMode,
|
||||
autoSendOnDictationEnd,
|
||||
}
|
||||
void FileSystem.writeAsStringAsync(WHISPER_SETTINGS_FILE, JSON.stringify(payload)).catch(() => {})
|
||||
}, [autoSendOnDictationEnd, defaultWhisperModel, transcriptionMode])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
const transcriber = whisperTranscriberRef.current
|
||||
whisperTranscriberRef.current = null
|
||||
if (transcriber) {
|
||||
void (async () => {
|
||||
await transcriber.stop().catch(() => {})
|
||||
await transcriber.release().catch(() => {})
|
||||
})()
|
||||
}
|
||||
|
||||
const bulkStream = bulkAudioStreamRef.current
|
||||
bulkAudioStreamRef.current = null
|
||||
if (bulkStream) {
|
||||
void (async () => {
|
||||
await bulkStream.stop().catch(() => {})
|
||||
await bulkStream.release().catch(() => {})
|
||||
})()
|
||||
}
|
||||
|
||||
const context = whisperContextRef.current
|
||||
whisperContextRef.current = null
|
||||
whisperContextModelRef.current = null
|
||||
|
||||
if (context) {
|
||||
void context.release().catch(() => {})
|
||||
}
|
||||
|
||||
void releaseAllWhisper().catch(() => {})
|
||||
}
|
||||
}, [])
|
||||
|
||||
const startWaveformPulse = useCallback(() => {
|
||||
if (waveformPulseIntervalRef.current) return
|
||||
|
||||
waveformPulseIntervalRef.current = setInterval(() => {
|
||||
if (!isRecordingRef.current) return
|
||||
|
||||
const next = waveformLevelsRef.current.map((value) => {
|
||||
const decay = value * 0.45
|
||||
const lift = Math.random() * 0.95
|
||||
return Math.max(0.08, Math.min(1, decay + lift * 0.55))
|
||||
})
|
||||
|
||||
waveformLevelsRef.current = next
|
||||
|
||||
const now = Date.now()
|
||||
if (now - lastWaveformCommitRef.current > 45) {
|
||||
setWaveformLevels(next)
|
||||
setWaveformTick(now)
|
||||
lastWaveformCommitRef.current = now
|
||||
}
|
||||
}, 70)
|
||||
}, [])
|
||||
|
||||
const finalizeRecordingState = useCallback(() => {
|
||||
isRecordingRef.current = false
|
||||
activeSessionRef.current = 0
|
||||
isStartingRef.current = false
|
||||
setIsRecording(false)
|
||||
stopWaveformPulse()
|
||||
clearWaveform()
|
||||
}, [clearWaveform, stopWaveformPulse])
|
||||
|
||||
const startRecording = useCallback(async () => {
|
||||
if (isRecordingRef.current || isStartingRef.current || downloadingModelID || isTranscribingBulk) return
|
||||
|
||||
isStartingRef.current = true
|
||||
const sessionID = Date.now()
|
||||
activeSessionRef.current = sessionID
|
||||
accumulatedRef.current = ""
|
||||
baseTextRef.current = normalizeTranscriptSessions(transcribedText)
|
||||
if (baseTextRef.current !== transcribedText) {
|
||||
setTranscribedText(baseTextRef.current)
|
||||
}
|
||||
isRecordingRef.current = true
|
||||
setIsRecording(true)
|
||||
setWhisperError("")
|
||||
|
||||
const cancelled = () => !isRecordingRef.current || activeSessionRef.current !== sessionID
|
||||
|
||||
try {
|
||||
const permission = await AudioManager.checkRecordingPermissions()
|
||||
const granted = permission === "Granted"
|
||||
setPermissionGranted(granted)
|
||||
setMicrophonePermissionState(granted ? "granted" : permission === "Denied" ? "denied" : "idle")
|
||||
|
||||
if (!granted) {
|
||||
setWhisperError("Microphone permission is required to record.")
|
||||
finalizeRecordingState()
|
||||
void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning).catch(() => {})
|
||||
return
|
||||
}
|
||||
|
||||
const sessionReady = await activateAudioSession("record")
|
||||
if (!sessionReady) {
|
||||
finalizeRecordingState()
|
||||
void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning).catch(() => {})
|
||||
return
|
||||
}
|
||||
|
||||
await ensureAudioInputRoute()
|
||||
|
||||
const context = await ensureWhisperModelReady(defaultWhisperModel)
|
||||
if (cancelled()) {
|
||||
isStartingRef.current = false
|
||||
return
|
||||
}
|
||||
|
||||
const previousTranscriber = whisperTranscriberRef.current
|
||||
whisperTranscriberRef.current = null
|
||||
if (previousTranscriber) {
|
||||
await previousTranscriber.stop().catch(() => {})
|
||||
await previousTranscriber.release().catch(() => {})
|
||||
}
|
||||
|
||||
const previousBulkStream = bulkAudioStreamRef.current
|
||||
bulkAudioStreamRef.current = null
|
||||
if (previousBulkStream) {
|
||||
await previousBulkStream.stop().catch(() => {})
|
||||
await previousBulkStream.release().catch(() => {})
|
||||
}
|
||||
|
||||
bulkAudioChunksRef.current = []
|
||||
bulkTranscriptionJobRef.current = 0
|
||||
|
||||
startWaveformPulse()
|
||||
|
||||
const englishOnlyModel = isEnglishOnlyWhisperModel(defaultWhisperModel)
|
||||
|
||||
if (transcriptionMode === "bulk") {
|
||||
const audioStream = new AudioPcmStreamAdapter()
|
||||
audioStream.onData((packet: unknown) => {
|
||||
if (activeSessionRef.current !== sessionID) return
|
||||
const data = (packet as { data?: unknown }).data
|
||||
if (!(data instanceof Uint8Array) || data.length === 0) return
|
||||
bulkAudioChunksRef.current.push(new Uint8Array(data))
|
||||
})
|
||||
audioStream.onError((error: string) => {
|
||||
if (activeSessionRef.current !== sessionID) return
|
||||
setWhisperError(error)
|
||||
console.error("[Dictation] Bulk audio stream error:", error)
|
||||
})
|
||||
|
||||
await audioStream.initialize({
|
||||
sampleRate: 16000,
|
||||
channels: 1,
|
||||
bitsPerSample: 16,
|
||||
bufferSize: 16 * 1024,
|
||||
audioSource: 6,
|
||||
})
|
||||
await audioStream.start()
|
||||
|
||||
bulkAudioStreamRef.current = audioStream
|
||||
|
||||
if (cancelled()) {
|
||||
await audioStream.stop().catch(() => {})
|
||||
await audioStream.release().catch(() => {})
|
||||
if (bulkAudioStreamRef.current === audioStream) {
|
||||
bulkAudioStreamRef.current = null
|
||||
}
|
||||
finalizeRecordingState()
|
||||
return
|
||||
}
|
||||
|
||||
isStartingRef.current = false
|
||||
return
|
||||
}
|
||||
|
||||
const transcriber = new RealtimeTranscriber(
|
||||
{
|
||||
whisperContext: context,
|
||||
audioStream: new AudioPcmStreamAdapter(),
|
||||
},
|
||||
{
|
||||
audioSliceSec: 4,
|
||||
audioMinSec: 0.8,
|
||||
maxSlicesInMemory: 6,
|
||||
transcribeOptions: {
|
||||
language: englishOnlyModel ? "en" : "auto",
|
||||
translate: !englishOnlyModel,
|
||||
maxLen: 1,
|
||||
},
|
||||
logger: () => {},
|
||||
},
|
||||
{
|
||||
onTranscribe: (event: RealtimeTranscribeEvent) => {
|
||||
if (activeSessionRef.current !== sessionID) return
|
||||
if (event.type !== "transcribe") return
|
||||
|
||||
const nextSessionText = mergeTranscriptChunk(accumulatedRef.current, event.data?.result ?? "")
|
||||
accumulatedRef.current = nextSessionText
|
||||
|
||||
const base = normalizeTranscriptSessions(baseTextRef.current)
|
||||
const separator = base.length > 0 && nextSessionText.length > 0 ? "\n\n" : ""
|
||||
setTranscribedText(normalizeTranscriptSessions(base + separator + nextSessionText))
|
||||
|
||||
if (nextSessionText.length > 0) {
|
||||
setHasCompletedSession(true)
|
||||
}
|
||||
},
|
||||
onError: (error: string) => {
|
||||
if (activeSessionRef.current !== sessionID) return
|
||||
console.error("[Dictation] Whisper realtime error:", error)
|
||||
setWhisperError(error)
|
||||
},
|
||||
onStatusChange: (active: boolean) => {
|
||||
if (activeSessionRef.current !== sessionID) return
|
||||
if (!active) {
|
||||
if (whisperTranscriberRef.current === transcriber) {
|
||||
whisperTranscriberRef.current = null
|
||||
}
|
||||
finalizeRecordingState()
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
whisperTranscriberRef.current = transcriber
|
||||
await transcriber.start()
|
||||
|
||||
if (cancelled()) {
|
||||
await transcriber.stop().catch(() => {})
|
||||
await transcriber.release().catch(() => {})
|
||||
if (whisperTranscriberRef.current === transcriber) {
|
||||
whisperTranscriberRef.current = null
|
||||
}
|
||||
finalizeRecordingState()
|
||||
return
|
||||
}
|
||||
|
||||
isStartingRef.current = false
|
||||
} catch (error) {
|
||||
const busy = isAudioSessionBusyError(error)
|
||||
const message = normalizeAudioStartErrorMessage(error)
|
||||
setWhisperError(message)
|
||||
|
||||
if (busy) {
|
||||
console.warn("[Dictation] Recording blocked while call is active")
|
||||
} else {
|
||||
console.error("[Dictation] Failed to start realtime transcription:", error)
|
||||
}
|
||||
|
||||
const activeTranscriber = whisperTranscriberRef.current
|
||||
whisperTranscriberRef.current = null
|
||||
if (activeTranscriber) {
|
||||
void (async () => {
|
||||
await activeTranscriber.stop().catch(() => {})
|
||||
await activeTranscriber.release().catch(() => {})
|
||||
})()
|
||||
}
|
||||
|
||||
finalizeRecordingState()
|
||||
void Haptics.notificationAsync(
|
||||
busy ? Haptics.NotificationFeedbackType.Warning : Haptics.NotificationFeedbackType.Error,
|
||||
).catch(() => {})
|
||||
}
|
||||
}, [
|
||||
defaultWhisperModel,
|
||||
downloadingModelID,
|
||||
ensureWhisperModelReady,
|
||||
finalizeRecordingState,
|
||||
isTranscribingBulk,
|
||||
activateAudioSession,
|
||||
ensureAudioInputRoute,
|
||||
startWaveformPulse,
|
||||
transcriptionMode,
|
||||
transcribedText,
|
||||
])
|
||||
|
||||
const stopRecording = useCallback(() => {
|
||||
if (!isRecordingRef.current && !isStartingRef.current) return
|
||||
|
||||
void Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {})
|
||||
|
||||
const baseAtStop = normalizeTranscriptSessions(baseTextRef.current)
|
||||
const englishOnlyModel = isEnglishOnlyWhisperModel(defaultWhisperModel)
|
||||
|
||||
const transcriber = whisperTranscriberRef.current
|
||||
whisperTranscriberRef.current = null
|
||||
if (transcriber) {
|
||||
void (async () => {
|
||||
await transcriber.stop().catch((error: unknown) => {
|
||||
console.warn("[Dictation] Failed to stop realtime transcription:", error)
|
||||
})
|
||||
await transcriber.release().catch(() => {})
|
||||
})()
|
||||
}
|
||||
|
||||
const bulkStream = bulkAudioStreamRef.current
|
||||
bulkAudioStreamRef.current = null
|
||||
const bulkChunks = bulkAudioChunksRef.current
|
||||
bulkAudioChunksRef.current = []
|
||||
|
||||
finalizeRecordingState()
|
||||
|
||||
if (transcriptionMode !== "bulk") {
|
||||
return
|
||||
}
|
||||
|
||||
const runID = Date.now()
|
||||
bulkTranscriptionJobRef.current = runID
|
||||
|
||||
void (async () => {
|
||||
if (bulkStream) {
|
||||
await bulkStream.stop().catch((error: unknown) => {
|
||||
console.warn("[Dictation] Failed to stop bulk audio stream:", error)
|
||||
})
|
||||
await bulkStream.release().catch(() => {})
|
||||
}
|
||||
|
||||
if (bulkChunks.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const totalLength = bulkChunks.reduce((sum, chunk) => sum + chunk.length, 0)
|
||||
if (totalLength === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const merged = new Uint8Array(totalLength)
|
||||
let offset = 0
|
||||
for (const chunk of bulkChunks) {
|
||||
merged.set(chunk, offset)
|
||||
offset += chunk.length
|
||||
}
|
||||
|
||||
const context = whisperContextRef.current
|
||||
if (!context) {
|
||||
setWhisperError("Whisper model is not loaded")
|
||||
return
|
||||
}
|
||||
|
||||
setIsTranscribingBulk(true)
|
||||
|
||||
try {
|
||||
const { promise } = context.transcribeData(merged.buffer, {
|
||||
language: englishOnlyModel ? "en" : "auto",
|
||||
translate: !englishOnlyModel,
|
||||
maxLen: 1,
|
||||
})
|
||||
|
||||
const result = await promise
|
||||
if (bulkTranscriptionJobRef.current !== runID) {
|
||||
return
|
||||
}
|
||||
|
||||
const sessionText = cleanSessionText(result.result ?? "")
|
||||
if (!sessionText) {
|
||||
return
|
||||
}
|
||||
|
||||
const separator = baseAtStop.length > 0 ? "\n\n" : ""
|
||||
setTranscribedText(normalizeTranscriptSessions(baseAtStop + separator + sessionText))
|
||||
setHasCompletedSession(true)
|
||||
} catch (error) {
|
||||
if (bulkTranscriptionJobRef.current !== runID) {
|
||||
return
|
||||
}
|
||||
const message = error instanceof Error ? error.message : "Bulk transcription failed"
|
||||
setWhisperError(message)
|
||||
console.error("[Dictation] Bulk transcription failed:", error)
|
||||
} finally {
|
||||
if (bulkTranscriptionJobRef.current === runID) {
|
||||
setIsTranscribingBulk(false)
|
||||
}
|
||||
}
|
||||
})()
|
||||
}, [defaultWhisperModel, finalizeRecordingState, transcriptionMode])
|
||||
|
||||
const clearIconRotation = useSharedValue(0)
|
||||
const sendOutProgress = useSharedValue(0)
|
||||
|
||||
const handleClearTranscript = useCallback(() => {
|
||||
void Haptics.selectionAsync().catch(() => {})
|
||||
|
||||
clearIconRotation.value = withSequence(
|
||||
withTiming(-30, { duration: 90 }),
|
||||
withTiming(30, { duration: 120 }),
|
||||
withTiming(0, { duration: 90 }),
|
||||
)
|
||||
|
||||
if (isRecordingRef.current) {
|
||||
stopRecording()
|
||||
}
|
||||
accumulatedRef.current = ""
|
||||
baseTextRef.current = ""
|
||||
setTranscribedText("")
|
||||
setHasCompletedSession(false)
|
||||
clearWaveform()
|
||||
sendOutProgress.value = 0
|
||||
setIsSending(false)
|
||||
}, [clearIconRotation, clearWaveform, sendOutProgress, stopRecording])
|
||||
|
||||
const handleHideAgentState = useCallback(() => {
|
||||
void Haptics.selectionAsync().catch(() => {})
|
||||
setReaderModeOpen(false)
|
||||
setAgentStateDismissed(true)
|
||||
}, [])
|
||||
|
||||
const handleOpenReaderMode = useCallback(() => {
|
||||
void Haptics.selectionAsync().catch(() => {})
|
||||
setReaderModeRendered(true)
|
||||
setReaderModeOpen(true)
|
||||
}, [])
|
||||
|
||||
const handleCloseReaderMode = useCallback(() => {
|
||||
void Haptics.selectionAsync().catch(() => {})
|
||||
setReaderModeOpen(false)
|
||||
}, [])
|
||||
|
||||
const handlePermissionDecision = useCallback(
|
||||
(reply: PermissionDecision) => {
|
||||
if (!activePermissionRequest || !activeServerId) return
|
||||
|
||||
void Haptics.selectionAsync().catch(() => {})
|
||||
void respondToPermission({
|
||||
serverID: activeServerId,
|
||||
sessionID: activePermissionRequest.sessionID,
|
||||
requestID: activePermissionRequest.id,
|
||||
reply,
|
||||
}).catch((error) => {
|
||||
Alert.alert(
|
||||
"Could not send decision",
|
||||
error instanceof Error ? error.message : "OpenCode did not accept that decision.",
|
||||
)
|
||||
})
|
||||
},
|
||||
[activePermissionRequest, activeServerId, respondToPermission],
|
||||
)
|
||||
|
||||
const resetTranscriptState = useCallback(() => {
|
||||
if (isRecordingRef.current) {
|
||||
stopRecording()
|
||||
}
|
||||
accumulatedRef.current = ""
|
||||
baseTextRef.current = ""
|
||||
setTranscribedText("")
|
||||
setHasCompletedSession(false)
|
||||
clearWaveform()
|
||||
}, [clearWaveform, stopRecording])
|
||||
|
||||
const handleOpenWhisperSettings = useCallback(() => {
|
||||
void Haptics.selectionAsync().catch(() => {})
|
||||
setDropdownMode("none")
|
||||
setWhisperSettingsOpen(true)
|
||||
}, [])
|
||||
|
||||
const handleDownloadWhisperModel = useCallback(
|
||||
async (modelID: WhisperModelID) => {
|
||||
const ok = await downloadWhisperModel(modelID)
|
||||
if (ok) {
|
||||
void Haptics.selectionAsync().catch(() => {})
|
||||
}
|
||||
},
|
||||
[downloadWhisperModel],
|
||||
)
|
||||
|
||||
const handleSelectWhisperModel = useCallback(
|
||||
async (modelID: WhisperModelID) => {
|
||||
if (isRecordingRef.current || isStartingRef.current) {
|
||||
stopRecording()
|
||||
}
|
||||
|
||||
try {
|
||||
await ensureWhisperModelReady(modelID)
|
||||
setDefaultWhisperModel(modelID)
|
||||
setWhisperError("")
|
||||
void Haptics.selectionAsync().catch(() => {})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Unable to switch Whisper model"
|
||||
setWhisperError(message)
|
||||
}
|
||||
},
|
||||
[ensureWhisperModelReady, stopRecording],
|
||||
)
|
||||
|
||||
const handleDeleteWhisperModel = useCallback(
|
||||
async (modelID: WhisperModelID) => {
|
||||
if (downloadingModelID === modelID) return
|
||||
|
||||
if (isRecordingRef.current || isStartingRef.current) {
|
||||
stopRecording()
|
||||
}
|
||||
|
||||
if (whisperContextModelRef.current === modelID && whisperContextRef.current) {
|
||||
const activeContext = whisperContextRef.current
|
||||
whisperContextRef.current = null
|
||||
whisperContextModelRef.current = null
|
||||
setActiveWhisperModel(null)
|
||||
await activeContext.release().catch(() => {})
|
||||
}
|
||||
|
||||
await FileSystem.deleteAsync(modelPath(modelID), { idempotent: true }).catch(() => {})
|
||||
const nextInstalled = await refreshInstalledWhisperModels()
|
||||
|
||||
if (defaultWhisperModel === modelID) {
|
||||
const fallbackModel = nextInstalled[0] ?? DEFAULT_WHISPER_MODEL
|
||||
setDefaultWhisperModel(fallbackModel)
|
||||
try {
|
||||
await ensureWhisperModelReady(fallbackModel)
|
||||
} catch {
|
||||
// Keep UI responsive if fallback init fails.
|
||||
}
|
||||
} else if (activeWhisperModel == null && nextInstalled.includes(defaultWhisperModel)) {
|
||||
try {
|
||||
await ensureWhisperModelReady(defaultWhisperModel)
|
||||
} catch {
|
||||
// Keep UI responsive if default model init fails.
|
||||
}
|
||||
}
|
||||
|
||||
void Haptics.selectionAsync().catch(() => {})
|
||||
},
|
||||
[
|
||||
activeWhisperModel,
|
||||
defaultWhisperModel,
|
||||
downloadingModelID,
|
||||
ensureWhisperModelReady,
|
||||
modelPath,
|
||||
refreshInstalledWhisperModels,
|
||||
stopRecording,
|
||||
],
|
||||
)
|
||||
|
||||
const handleRequestNotificationPermission = useCallback(async () => {
|
||||
if (notificationPermissionState === "pending") return
|
||||
|
||||
setNotificationPermissionState("pending")
|
||||
|
||||
try {
|
||||
const granted = await ensureNotificationPermissions()
|
||||
setNotificationPermissionState(granted ? "granted" : "denied")
|
||||
|
||||
if (!granted) {
|
||||
return
|
||||
}
|
||||
|
||||
const token = await getDevicePushToken()
|
||||
if (token) {
|
||||
setDevicePushToken(token)
|
||||
}
|
||||
} catch {
|
||||
setNotificationPermissionState("denied")
|
||||
}
|
||||
}, [notificationPermissionState, setDevicePushToken])
|
||||
|
||||
const handleRequestMicrophonePermission = useCallback(async () => {
|
||||
if (microphonePermissionState === "pending") return
|
||||
|
||||
setMicrophonePermissionState("pending")
|
||||
|
||||
try {
|
||||
const permission = await AudioManager.requestRecordingPermissions()
|
||||
const granted = permission === "Granted"
|
||||
setPermissionGranted(granted)
|
||||
setMicrophonePermissionState(granted ? "granted" : "denied")
|
||||
|
||||
if (granted) {
|
||||
await ensureAudioInputRoute()
|
||||
}
|
||||
} catch {
|
||||
setPermissionGranted(false)
|
||||
setMicrophonePermissionState("denied")
|
||||
}
|
||||
}, [ensureAudioInputRoute, microphonePermissionState])
|
||||
|
||||
const handleRequestLocalNetworkPermission = useCallback(async () => {
|
||||
if (localNetworkPermissionState === "pending") return
|
||||
|
||||
setLocalNetworkPermissionState("pending")
|
||||
|
||||
const localProbes = new Set<string>([
|
||||
"http://192.168.1.1",
|
||||
"http://192.168.0.1",
|
||||
"http://10.0.0.1",
|
||||
"http://100.100.100.100",
|
||||
])
|
||||
|
||||
for (const server of serversRef.current) {
|
||||
try {
|
||||
const url = new URL(server.url)
|
||||
if (looksLikeLocalHost(url.hostname)) {
|
||||
localProbes.add(`${url.protocol}//${url.host}`)
|
||||
}
|
||||
} catch {
|
||||
// Skip malformed saved server URL.
|
||||
}
|
||||
}
|
||||
|
||||
const controller = new AbortController()
|
||||
const timeout = setTimeout(() => {
|
||||
controller.abort()
|
||||
}, 1800)
|
||||
|
||||
try {
|
||||
await Promise.allSettled(
|
||||
[...localProbes].map((base) =>
|
||||
expoFetch(`${base.replace(/\/+$/, "")}/health`, {
|
||||
method: "GET",
|
||||
signal: controller.signal,
|
||||
}),
|
||||
),
|
||||
)
|
||||
setLocalNetworkPermissionState("granted")
|
||||
} catch {
|
||||
setLocalNetworkPermissionState("denied")
|
||||
} finally {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
}, [localNetworkPermissionState, serversRef])
|
||||
|
||||
const completeSend = useCallback(() => {
|
||||
if (sendSettleTimeoutRef.current) {
|
||||
clearTimeout(sendSettleTimeoutRef.current)
|
||||
}
|
||||
|
||||
sendSettleTimeoutRef.current = setTimeout(() => {
|
||||
resetTranscriptState()
|
||||
sendOutProgress.value = 0
|
||||
setIsSending(false)
|
||||
sendSettleTimeoutRef.current = null
|
||||
}, SEND_SETTLE_MS)
|
||||
}, [resetTranscriptState, sendOutProgress])
|
||||
|
||||
const handleSendTranscript = useCallback(async () => {
|
||||
const text = transcribedText.trim()
|
||||
if (text.length === 0 || isSending || !activeServerId || !activeSessionId) return
|
||||
|
||||
const server = serversRef.current.find((item) => item.id === activeServerId)
|
||||
if (!server) return
|
||||
|
||||
const session = server.sessions.find((item) => item.id === activeSessionId)
|
||||
if (!session) return
|
||||
|
||||
const base = server.url.replace(/\/+$/, "")
|
||||
|
||||
setIsSending(true)
|
||||
setMonitorStatus("Sending prompt…")
|
||||
|
||||
try {
|
||||
const response = await fetch(`${base}/session/${session.id}/prompt_async`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
parts: [
|
||||
{
|
||||
type: "text",
|
||||
text,
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Prompt request failed (${response.status})`)
|
||||
}
|
||||
|
||||
setLatestPromptText(text)
|
||||
|
||||
const nextJob: MonitorJob = {
|
||||
id: `job-${Date.now()}`,
|
||||
sessionID: session.id,
|
||||
opencodeBaseURL: base,
|
||||
startedAt: Date.now(),
|
||||
}
|
||||
|
||||
await beginMonitoring(nextJob)
|
||||
|
||||
if (server.relaySecret.trim().length === 0) {
|
||||
setMonitorStatus("Monitoring (foreground only)")
|
||||
}
|
||||
|
||||
void sendPlayer.seekTo(0)
|
||||
void sendPlayer.play()
|
||||
|
||||
void Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Heavy).catch(() => {})
|
||||
setTimeout(() => {
|
||||
void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {})
|
||||
}, 70)
|
||||
|
||||
sendOutProgress.value = withTiming(
|
||||
1,
|
||||
{
|
||||
duration: 320,
|
||||
easing: Easing.bezier(0.2, 0.8, 0.2, 1),
|
||||
},
|
||||
(finished) => {
|
||||
if (finished) {
|
||||
runOnJS(completeSend)()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// Safety timeout: if the Reanimated animation callback never fires (e.g. app
|
||||
// backgrounded during the 320ms animation), force-reset isSending so the user
|
||||
// isn't permanently blocked from sending new prompts.
|
||||
setTimeout(() => {
|
||||
completeSend()
|
||||
}, 5_000)
|
||||
} catch {
|
||||
setMonitorStatus("Failed to send prompt")
|
||||
void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {})
|
||||
setIsSending(false)
|
||||
sendOutProgress.value = 0
|
||||
}
|
||||
}, [
|
||||
activeServerId,
|
||||
activeSessionId,
|
||||
beginMonitoring,
|
||||
completeSend,
|
||||
isSending,
|
||||
serversRef,
|
||||
setMonitorStatus,
|
||||
setLatestPromptText,
|
||||
sendOutProgress,
|
||||
sendPlayer,
|
||||
transcribedText,
|
||||
])
|
||||
|
||||
// --- Gesture handling: tap vs hold ---
|
||||
|
||||
const handlePressIn = useCallback(() => {
|
||||
pressInTimeRef.current = Date.now()
|
||||
|
||||
if (isRecordingRef.current) return
|
||||
|
||||
setDropdownMode("none")
|
||||
void Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {})
|
||||
isHoldingRef.current = true
|
||||
// Snap pager to live page (index 0) so user sees their transcription
|
||||
if (promptPagerRef.current) {
|
||||
try {
|
||||
promptPagerRef.current.scrollToIndex({ index: 0, animated: true })
|
||||
} catch {
|
||||
// FlatList may not have items yet
|
||||
}
|
||||
}
|
||||
void startRecording()
|
||||
}, [startRecording])
|
||||
|
||||
const handlePressOut = useCallback(() => {
|
||||
const pressDuration = Date.now() - pressInTimeRef.current
|
||||
|
||||
if (pressDuration < TAP_THRESHOLD_MS) {
|
||||
if (isHoldingRef.current) {
|
||||
// Tap started recording on pressIn -- keep it running (toggle ON)
|
||||
isHoldingRef.current = false
|
||||
} else {
|
||||
// Already recording from a previous tap -- this tap stops it
|
||||
stopRecording()
|
||||
}
|
||||
} else {
|
||||
// Long press = hold-to-record, stop on release
|
||||
isHoldingRef.current = false
|
||||
stopRecording()
|
||||
}
|
||||
}, [stopRecording])
|
||||
|
||||
const modelDownloading = downloadingModelID !== null
|
||||
const modelLoading = isPreparingWhisperModel || activeWhisperModel == null || modelDownloading || isTranscribingBulk
|
||||
const dictationSettingsLocked = isRecording || isTranscribingBulk || isSending
|
||||
let modelLoadingState: "downloading" | "loading" | "ready" = "ready"
|
||||
if (modelDownloading) {
|
||||
modelLoadingState = "downloading"
|
||||
} else if (modelLoading) {
|
||||
modelLoadingState = "loading"
|
||||
}
|
||||
const pct = Math.round(Math.max(0, Math.min(1, downloadProgress)) * 100)
|
||||
const loadingModelLabel = downloadingModelID
|
||||
? WHISPER_MODEL_LABELS[downloadingModelID]
|
||||
: WHISPER_MODEL_LABELS[defaultWhisperModel]
|
||||
const hasTranscript = transcribedText.trim().length > 0
|
||||
const hasAssistantResponse = latestAssistantResponse.trim().length > 0
|
||||
const readerBlocks = useMemo(() => parseReaderBlocks(latestAssistantResponse), [latestAssistantResponse])
|
||||
const activePermissionCard = activePermissionRequest ? buildPermissionCardModel(activePermissionRequest) : null
|
||||
const hasPendingPermission = activePermissionRequest !== null && activePermissionCard !== null
|
||||
const readerModeEnabled = readerModeOpen && hasAssistantResponse && !hasPendingPermission
|
||||
const readerModeVisible = readerModeEnabled || readerModeRendered
|
||||
const fallbackAgentStateCardHeight = transcriptionAreaHeight > 0 ? Math.max(0, (transcriptionAreaHeight - 8) / 2) : 0
|
||||
const collapsedReaderHeight = agentStateCardHeight > 0 ? agentStateCardHeight : fallbackAgentStateCardHeight
|
||||
const expandedReaderHeight = Math.max(collapsedReaderHeight, transcriptionAreaHeight)
|
||||
const hasAgentActivity = hasAssistantResponse || monitorStatus.trim().length > 0 || monitorJob !== null
|
||||
const shouldShowAgentStateCard = !hasPendingPermission && hasAgentActivity && !agentStateDismissed
|
||||
const showsCompleteState = monitorStatus.toLowerCase().includes("complete")
|
||||
let agentStateIcon: "loading" | "done" = "loading"
|
||||
if (monitorJob === null && (hasAssistantResponse || showsCompleteState)) {
|
||||
agentStateIcon = "done"
|
||||
}
|
||||
const agentStateText = hasAssistantResponse ? latestAssistantResponse : "Waiting for agent…"
|
||||
const agentStateBlocks = useMemo(() => parseReaderBlocks(agentStateText), [agentStateText])
|
||||
const shouldShowSend = hasCompletedSession && hasTranscript && !hasPendingPermission
|
||||
const activeServer = servers.find((s) => s.id === activeServerId) ?? null
|
||||
const discoveredServerOptions = useMemo(() => {
|
||||
const saved = new Set(servers.map((server) => server.url.replace(/\/+$/, "")))
|
||||
return discoveredServers.filter((server) => !saved.has(server.url.replace(/\/+$/, "")))
|
||||
}, [discoveredServers, servers])
|
||||
const discoveredServerEmptyLabel =
|
||||
discoveryStatus === "error"
|
||||
? "Unable to discover local servers"
|
||||
: discoveryStatus === "scanning"
|
||||
? "Scanning local network..."
|
||||
: "No local servers found"
|
||||
const activeSession = activeServer?.sessions.find((s) => s.id === activeSessionId) ?? null
|
||||
let currentSessionModelLabel = "Not available"
|
||||
if (latestAssistantContext?.modelID) {
|
||||
currentSessionModelLabel = latestAssistantContext.modelID
|
||||
if (latestAssistantContext.providerID) {
|
||||
currentSessionModelLabel = `${latestAssistantContext.providerID}/${latestAssistantContext.modelID}`
|
||||
}
|
||||
}
|
||||
const currentSessionDirectory = latestAssistantContext?.workingDirectory ?? activeSession?.directory
|
||||
const currentSessionUpdated = activeSession ? formatSessionUpdated(activeSession.updated) : ""
|
||||
const sessionList = activeSession
|
||||
? (activeServer?.sessions ?? []).filter((session) => session.id !== activeSession.id)
|
||||
: (activeServer?.sessions ?? [])
|
||||
const canSendToSession = !!activeServer && activeServer.status === "online" && !!activeSession
|
||||
const isReplyingToActivePermission =
|
||||
activePermissionRequest !== null && respondingPermissionID === activePermissionRequest.id
|
||||
const displayedTranscript = isSending ? "" : transcribedText
|
||||
const [transcriptionPanelWidth, setTranscriptionPanelWidth] = useState(0)
|
||||
const handleTranscriptionPanelLayout = useCallback((e: LayoutChangeEvent) => {
|
||||
setTranscriptionPanelWidth(e.nativeEvent.layout.width)
|
||||
}, [])
|
||||
const pagerPageWidth = transcriptionPanelWidth || 1
|
||||
|
||||
// Prompt history pager: "live" at index 0 (leftmost), then history newest-first to the right.
|
||||
// Swipe right-to-left to browse older prompts, swipe left-to-right to return to live.
|
||||
const promptPagerData = useMemo<(PromptHistoryEntry | "live")[]>(
|
||||
() => (promptHistory.length > 0 ? ["live" as const, ...[...promptHistory].reverse()] : []),
|
||||
[promptHistory],
|
||||
)
|
||||
const promptPagerKeyExtractor = useCallback(
|
||||
(item: PromptHistoryEntry | "live") => (item === "live" ? "live" : item.userMessageID),
|
||||
[],
|
||||
)
|
||||
const handlePromptPagerSnap = useCallback(
|
||||
(e: { nativeEvent: { contentOffset: { x: number } } }) => {
|
||||
const pageIndex = Math.round(e.nativeEvent.contentOffset.x / pagerPageWidth)
|
||||
if (pageIndex !== promptPagerPageRef.current) {
|
||||
promptPagerPageRef.current = pageIndex
|
||||
void Haptics.selectionAsync().catch(() => {})
|
||||
}
|
||||
},
|
||||
[pagerPageWidth],
|
||||
)
|
||||
const isDropdownOpen = dropdownMode !== "none"
|
||||
const effectiveDropdownMode = isDropdownOpen ? dropdownMode : dropdownRenderMode
|
||||
const isCreatingSession = sessionCreateMode !== null
|
||||
const showSessionCreationChoices =
|
||||
effectiveDropdownMode === "session" && !!activeServer && activeServer.status === "online"
|
||||
const sessionCreationChoiceCount = showSessionCreationChoices ? (activeSession ? 2 : 1) : 0
|
||||
const recommendedPairHostURL = useMemo(() => {
|
||||
const online = pairHostOptions
|
||||
.map((item) => ({ item, probe: pairHostProbes[item.url] }))
|
||||
.filter((entry) => entry.probe?.status === "online")
|
||||
.sort(
|
||||
(a, b) => (a.probe?.latencyMs ?? Number.POSITIVE_INFINITY) - (b.probe?.latencyMs ?? Number.POSITIVE_INFINITY),
|
||||
)
|
||||
|
||||
if (online[0]) {
|
||||
return online[0].item.url
|
||||
}
|
||||
|
||||
return pairHostOptions[0]?.url ?? null
|
||||
}, [pairHostOptions, pairHostProbes])
|
||||
const headerTitle = activeServer?.name ?? "No server configured"
|
||||
let headerDotStyle = styles.serverStatusOffline
|
||||
if (activeServer?.status === "online") {
|
||||
headerDotStyle = styles.serverStatusActive
|
||||
} else if (activeServer?.status === "checking") {
|
||||
headerDotStyle = styles.serverStatusChecking
|
||||
}
|
||||
|
||||
const recordingProgress = useSharedValue(0)
|
||||
const sendVisibility = useSharedValue(hasTranscript ? 1 : 0)
|
||||
const waveformVisibility = useSharedValue(0)
|
||||
const serverMenuProgress = useSharedValue(0)
|
||||
const readerExpandProgress = useSharedValue(0)
|
||||
|
||||
useEffect(() => {
|
||||
recordingProgress.value = withSpring(isRecording ? 1 : 0, {
|
||||
damping: 14,
|
||||
stiffness: 140,
|
||||
mass: 0.8,
|
||||
})
|
||||
}, [isRecording, recordingProgress])
|
||||
|
||||
useEffect(() => {
|
||||
const isGenerating = isRecording
|
||||
waveformVisibility.value = withTiming(isGenerating ? 1 : 0, {
|
||||
duration: isGenerating ? 180 : 240,
|
||||
easing: Easing.inOut(Easing.quad),
|
||||
})
|
||||
}, [isRecording, waveformVisibility])
|
||||
|
||||
useEffect(() => {
|
||||
serverMenuProgress.value = withTiming(isDropdownOpen ? 1 : 0, {
|
||||
duration: isDropdownOpen ? 240 : 240,
|
||||
easing: isDropdownOpen ? Easing.bezier(0.2, 0.8, 0.2, 1) : Easing.bezier(0.4, 0, 0.2, 1),
|
||||
})
|
||||
}, [isDropdownOpen, serverMenuProgress])
|
||||
|
||||
useEffect(() => {
|
||||
if (readerModeEnabled) {
|
||||
readerExpandProgress.value = withTiming(1, {
|
||||
duration: 320,
|
||||
easing: Easing.bezier(0.22, 1, 0.36, 1),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!readerModeRendered) {
|
||||
readerExpandProgress.value = 0
|
||||
return
|
||||
}
|
||||
|
||||
readerExpandProgress.value = withTiming(
|
||||
0,
|
||||
{
|
||||
duration: 240,
|
||||
easing: Easing.bezier(0.4, 0, 0.2, 1),
|
||||
},
|
||||
(finished) => {
|
||||
if (finished) {
|
||||
runOnJS(setReaderModeRendered)(false)
|
||||
}
|
||||
},
|
||||
)
|
||||
}, [readerExpandProgress, readerModeEnabled, readerModeRendered])
|
||||
|
||||
useEffect(() => {
|
||||
if (dropdownMode !== "none") {
|
||||
setDropdownRenderMode(dropdownMode)
|
||||
}
|
||||
}, [dropdownMode])
|
||||
|
||||
useEffect(() => {
|
||||
sendVisibility.value = shouldShowSend
|
||||
? withTiming(1, {
|
||||
duration: 320,
|
||||
easing: Easing.bezier(0.2, 0.8, 0.2, 1),
|
||||
})
|
||||
: withTiming(0, {
|
||||
duration: 360,
|
||||
easing: Easing.bezier(0.22, 0.61, 0.36, 1),
|
||||
})
|
||||
}, [shouldShowSend, sendVisibility])
|
||||
|
||||
useEffect(() => {
|
||||
const text = transcribedText.trim()
|
||||
if (!hasCompletedSession || text.length === 0) {
|
||||
autoSendSignatureRef.current = ""
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
!autoSendOnDictationEnd ||
|
||||
isRecording ||
|
||||
isTranscribingBulk ||
|
||||
isSending ||
|
||||
hasPendingPermission ||
|
||||
!activeServerId ||
|
||||
!activeSessionId
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const signature = `${activeServerId}:${activeSessionId}:${transcriptionMode}:${text}`
|
||||
if (autoSendSignatureRef.current === signature) {
|
||||
return
|
||||
}
|
||||
|
||||
autoSendSignatureRef.current = signature
|
||||
void handleSendTranscript()
|
||||
}, [
|
||||
activeServerId,
|
||||
activeSessionId,
|
||||
autoSendOnDictationEnd,
|
||||
handleSendTranscript,
|
||||
hasCompletedSession,
|
||||
hasPendingPermission,
|
||||
isRecording,
|
||||
isSending,
|
||||
isTranscribingBulk,
|
||||
transcriptionMode,
|
||||
transcribedText,
|
||||
])
|
||||
|
||||
// Parent clips outer half of center-stroke, so only inner half is visible.
|
||||
// borderWidth 6 → 3px visible inward, borderWidth 12 → 6px visible inward.
|
||||
const animatedBorderStyle = useAnimatedStyle(() => {
|
||||
const progress = recordingProgress.value
|
||||
// Width: 3 → ~1.5px visible inward at rest (matches other cards),
|
||||
// 12 → ~6px visible inward when active
|
||||
const bw = interpolate(progress, [0, 1], [3, 12], Extrapolation.CLAMP)
|
||||
return {
|
||||
borderWidth: bw,
|
||||
borderColor: "#FF2E3F",
|
||||
}
|
||||
})
|
||||
|
||||
const animatedDotStyle = useAnimatedStyle(() => ({
|
||||
borderRadius: interpolate(recordingProgress.value, [0, 1], [19, 2], Extrapolation.CLAMP),
|
||||
}))
|
||||
|
||||
const animatedClearIconStyle = useAnimatedStyle(() => ({
|
||||
transform: [{ rotate: `${clearIconRotation.value}deg` }],
|
||||
}))
|
||||
|
||||
const animatedSendStyle = useAnimatedStyle(() => ({
|
||||
width: interpolate(sendVisibility.value, [0, 1], [0, Math.max((controlsWidth - 8) / 2, 0)], Extrapolation.CLAMP),
|
||||
marginLeft: interpolate(sendVisibility.value, [0, 1], [0, 8], Extrapolation.CLAMP),
|
||||
opacity: sendVisibility.value,
|
||||
transform: [
|
||||
{
|
||||
translateX: interpolate(sendVisibility.value, [0, 1], [14, 0], Extrapolation.CLAMP),
|
||||
},
|
||||
{
|
||||
scale: interpolate(sendVisibility.value, [0, 1], [0.98, 1], Extrapolation.CLAMP),
|
||||
},
|
||||
],
|
||||
}))
|
||||
|
||||
const animatedTranscriptSendStyle = useAnimatedStyle(() => ({
|
||||
opacity: interpolate(sendOutProgress.value, [0, 1], [1, 0], Extrapolation.CLAMP),
|
||||
transform: [
|
||||
{
|
||||
translateY: interpolate(sendOutProgress.value, [0, 1], [0, -44], Extrapolation.CLAMP),
|
||||
},
|
||||
],
|
||||
}))
|
||||
|
||||
const animatedWaveformRowStyle = useAnimatedStyle(() => ({
|
||||
opacity: waveformVisibility.value,
|
||||
transform: [
|
||||
{
|
||||
translateY: interpolate(waveformVisibility.value, [0, 1], [6, 0], Extrapolation.CLAMP),
|
||||
},
|
||||
],
|
||||
}))
|
||||
|
||||
// Inverse of waveform: visible when waveform is hidden, fades out when waveform appears
|
||||
const animatedSwipeHintStyle = useAnimatedStyle(() => ({
|
||||
opacity: interpolate(waveformVisibility.value, [0, 1], [1, 0], Extrapolation.CLAMP),
|
||||
}))
|
||||
|
||||
const maxDropdownListHeight = DROPDOWN_VISIBLE_ROWS * DROPDOWN_ROW_HEIGHT
|
||||
const serverMenuEntries = Math.max(servers.length, 1) + Math.max(discoveredServerOptions.length, 1)
|
||||
const estimatedServerMenuRowsHeight = Math.min(
|
||||
SERVER_MENU_SECTION_HEIGHT + serverMenuEntries * SERVER_MENU_ENTRY_HEIGHT,
|
||||
maxDropdownListHeight,
|
||||
)
|
||||
const sessionMenuRows = Math.max(activeServer?.sessions.length ?? 0, 1)
|
||||
const estimatedSessionMenuRowsHeight = Math.min(sessionMenuRows, DROPDOWN_VISIBLE_ROWS) * DROPDOWN_ROW_HEIGHT
|
||||
const serverMenuRowsHeight = Math.min(serverMenuListHeight || estimatedServerMenuRowsHeight, maxDropdownListHeight)
|
||||
const sessionMenuRowsHeight = Math.min(sessionMenuListHeight || estimatedSessionMenuRowsHeight, maxDropdownListHeight)
|
||||
const expandedRowsHeight = effectiveDropdownMode === "server" ? serverMenuRowsHeight : sessionMenuRowsHeight
|
||||
|
||||
const estimatedSessionFooterHeight = sessionCreationChoiceCount === 2 ? 72 : sessionCreationChoiceCount === 1 ? 38 : 8
|
||||
|
||||
const measuredServerFooterHeight = serverMenuFooterHeight || SERVER_MENU_FOOTER_HEIGHT
|
||||
const measuredSessionFooterHeight = sessionMenuFooterHeight || estimatedSessionFooterHeight
|
||||
|
||||
const dropdownFooterExtraHeight =
|
||||
effectiveDropdownMode === "server"
|
||||
? measuredServerFooterHeight
|
||||
: showSessionCreationChoices
|
||||
? measuredSessionFooterHeight
|
||||
: 8
|
||||
const expandedHeaderHeight = 51 + 12 + expandedRowsHeight + dropdownFooterExtraHeight
|
||||
|
||||
const animatedHeaderStyle = useAnimatedStyle(() => ({
|
||||
height: interpolate(serverMenuProgress.value, [0, 1], [51, expandedHeaderHeight], Extrapolation.CLAMP),
|
||||
}))
|
||||
|
||||
const animatedServerMenuStyle = useAnimatedStyle(() => ({
|
||||
opacity: serverMenuProgress.value,
|
||||
transform: [
|
||||
{
|
||||
translateY: interpolate(serverMenuProgress.value, [0, 1], [-8, 0], Extrapolation.CLAMP),
|
||||
},
|
||||
],
|
||||
}))
|
||||
|
||||
const animatedHeaderShadowStyle = useAnimatedStyle(() => ({
|
||||
shadowOpacity: interpolate(serverMenuProgress.value, [0, 1], [0, 0.35], Extrapolation.CLAMP),
|
||||
shadowRadius: interpolate(serverMenuProgress.value, [0, 1], [0, 18], Extrapolation.CLAMP),
|
||||
elevation: interpolate(serverMenuProgress.value, [0, 1], [0, 16], Extrapolation.CLAMP),
|
||||
}))
|
||||
|
||||
const animatedReaderExpandStyle = useAnimatedStyle(() => ({
|
||||
height: interpolate(
|
||||
readerExpandProgress.value,
|
||||
[0, 1],
|
||||
[collapsedReaderHeight, expandedReaderHeight],
|
||||
Extrapolation.CLAMP,
|
||||
),
|
||||
opacity: interpolate(readerExpandProgress.value, [0, 0.12, 1], [0, 1, 1], Extrapolation.CLAMP),
|
||||
}))
|
||||
|
||||
const animatedAgentStateActionsStyle = useAnimatedStyle(() => ({
|
||||
opacity: interpolate(readerExpandProgress.value, [0, 0.16, 1], [1, 0, 0], Extrapolation.CLAMP),
|
||||
}))
|
||||
|
||||
const animatedReaderToggleTravelStyle = useAnimatedStyle(() => ({
|
||||
transform: [
|
||||
{
|
||||
translateX: interpolate(readerExpandProgress.value, [0, 1], [-READER_ACTION_TRAVEL, 0], Extrapolation.CLAMP),
|
||||
},
|
||||
],
|
||||
}))
|
||||
|
||||
const animatedReaderToggleOpenIconStyle = useAnimatedStyle(() => ({
|
||||
opacity: interpolate(readerExpandProgress.value, [0, 0.45, 1], [1, 0.18, 0], Extrapolation.CLAMP),
|
||||
transform: [{ scale: interpolate(readerExpandProgress.value, [0, 1], [1, 0.92], Extrapolation.CLAMP) }],
|
||||
}))
|
||||
|
||||
const animatedReaderToggleCloseIconStyle = useAnimatedStyle(() => ({
|
||||
opacity: interpolate(readerExpandProgress.value, [0, 0.45, 1], [0, 0.35, 1], Extrapolation.CLAMP),
|
||||
transform: [{ scale: interpolate(readerExpandProgress.value, [0, 1], [0.92, 1], Extrapolation.CLAMP) }],
|
||||
}))
|
||||
|
||||
const waveformColumnMeta = useMemo(
|
||||
() =>
|
||||
Array.from({ length: waveformLevels.length }, () => ({
|
||||
delay: Math.random() * 1.5,
|
||||
duration: 1 + Math.random(),
|
||||
phase: Math.random() * Math.PI * 2,
|
||||
})),
|
||||
[waveformLevels.length],
|
||||
)
|
||||
|
||||
const getWaveformCellStyle = useCallback(
|
||||
(row: number, col: number) => {
|
||||
const level = waveformLevels[col] ?? 0
|
||||
const rowFromBottom = WAVEFORM_ROWS - 1 - row
|
||||
const intensity = Math.max(0, Math.min(1, level * WAVEFORM_ROWS - rowFromBottom))
|
||||
|
||||
const meta = waveformColumnMeta[col]
|
||||
const t = waveformTick / 1000
|
||||
const basePhase = (Math.max(0, t - meta.delay) / meta.duration) * Math.PI * 2 + meta.phase + row * 0.35
|
||||
const pulse = 0.5 + 0.5 * Math.sin(basePhase)
|
||||
|
||||
let alpha = 0.08
|
||||
if (intensity > 0) {
|
||||
alpha = (0.4 + intensity * 0.6) * (0.85 + pulse * 0.15)
|
||||
} else if (isRecording) {
|
||||
alpha = 0.1 + pulse * 0.07
|
||||
}
|
||||
|
||||
// Base palette around #78839A, with brighter/lower variants by intensity.
|
||||
const baseR = 120
|
||||
const baseG = 131
|
||||
const baseB = 154
|
||||
const lift = Math.round(intensity * 28)
|
||||
const r = Math.min(255, baseR + lift)
|
||||
const g = Math.min(255, baseG + lift)
|
||||
const b = Math.min(255, baseB + lift)
|
||||
|
||||
return {
|
||||
backgroundColor: `rgba(${r}, ${g}, ${b}, ${alpha})`,
|
||||
borderColor: `rgba(${Math.min(255, r + 8)}, ${Math.min(255, g + 8)}, ${Math.min(255, b + 8)}, ${Math.min(1, alpha + 0.16)})`,
|
||||
}
|
||||
},
|
||||
[isRecording, waveformColumnMeta, waveformLevels, waveformTick],
|
||||
)
|
||||
|
||||
const handleControlsLayout = useCallback((event: LayoutChangeEvent) => {
|
||||
setControlsWidth(event.nativeEvent.layout.width)
|
||||
}, [])
|
||||
|
||||
const handleTranscriptionAreaLayout = useCallback((event: LayoutChangeEvent) => {
|
||||
const next = Math.ceil(event.nativeEvent.layout.height)
|
||||
setTranscriptionAreaHeight((prev) => (prev === next ? prev : next))
|
||||
}, [])
|
||||
|
||||
const handleAgentStateCardLayout = useCallback((event: LayoutChangeEvent) => {
|
||||
const next = Math.ceil(event.nativeEvent.layout.height)
|
||||
setAgentStateCardHeight((prev) => (prev === next ? prev : next))
|
||||
}, [])
|
||||
|
||||
const handleWaveformLayout = useCallback((event: LayoutChangeEvent) => {
|
||||
const width = event.nativeEvent.layout.width
|
||||
const columns = Math.max(14, Math.floor((width + WAVEFORM_CELL_GAP) / (WAVEFORM_CELL_SIZE + WAVEFORM_CELL_GAP)))
|
||||
|
||||
if (columns === waveformLevelsRef.current.length) return
|
||||
|
||||
const next = Array.from({ length: columns }, () => 0)
|
||||
waveformLevelsRef.current = next
|
||||
setWaveformLevels(next)
|
||||
}, [])
|
||||
|
||||
const handleServerMenuListLayout = useCallback((event: LayoutChangeEvent) => {
|
||||
const next = Math.ceil(event.nativeEvent.layout.height)
|
||||
setServerMenuListHeight((prev) => (prev === next ? prev : next))
|
||||
}, [])
|
||||
|
||||
const handleSessionMenuListLayout = useCallback((event: LayoutChangeEvent) => {
|
||||
const next = Math.ceil(event.nativeEvent.layout.height)
|
||||
setSessionMenuListHeight((prev) => (prev === next ? prev : next))
|
||||
}, [])
|
||||
|
||||
const handleServerMenuFooterLayout = useCallback((event: LayoutChangeEvent) => {
|
||||
const next = Math.ceil(event.nativeEvent.layout.height)
|
||||
setServerMenuFooterHeight((prev) => (prev === next ? prev : next))
|
||||
}, [])
|
||||
|
||||
const handleSessionMenuFooterLayout = useCallback((event: LayoutChangeEvent) => {
|
||||
const next = Math.ceil(event.nativeEvent.layout.height)
|
||||
setSessionMenuFooterHeight((prev) => (prev === next ? prev : next))
|
||||
}, [])
|
||||
|
||||
const renderMarkdownBlocks = (blocks: ReaderBlock[], variant: "reader" | "reply") => {
|
||||
const keyPrefix = variant === "reader" ? "reader" : "reply"
|
||||
const paragraphStyle = variant === "reader" ? styles.readerParagraph : styles.replyText
|
||||
|
||||
const renderMarkdownInline = (input: string, blockIndex: number) =>
|
||||
parseReaderInlineSegments(input).map((segment, segmentIndex) => {
|
||||
const segmentKey = `${keyPrefix}-inline-${blockIndex}-${segmentIndex}`
|
||||
|
||||
switch (segment.type) {
|
||||
case "inline_code":
|
||||
return (
|
||||
<Text key={segmentKey} style={styles.readerInlineCode}>
|
||||
{segment.content}
|
||||
</Text>
|
||||
)
|
||||
case "italic":
|
||||
return (
|
||||
<Text key={segmentKey} style={styles.markdownItalic}>
|
||||
{segment.content}
|
||||
</Text>
|
||||
)
|
||||
case "bold":
|
||||
return (
|
||||
<Text key={segmentKey} style={styles.markdownBold}>
|
||||
{segment.content}
|
||||
</Text>
|
||||
)
|
||||
case "bold_italic":
|
||||
return (
|
||||
<Text key={segmentKey} style={styles.markdownBoldItalic}>
|
||||
{segment.content}
|
||||
</Text>
|
||||
)
|
||||
default:
|
||||
return <Text key={segmentKey}>{segment.content}</Text>
|
||||
}
|
||||
})
|
||||
|
||||
const getHeadingStyle = (level: ReaderHeadingLevel) => {
|
||||
if (variant === "reader") {
|
||||
return [
|
||||
styles.readerHeading,
|
||||
level === 1 ? styles.readerHeading1 : level === 2 ? styles.readerHeading2 : styles.readerHeading3,
|
||||
]
|
||||
}
|
||||
|
||||
return [
|
||||
styles.replyHeading,
|
||||
level === 1 ? styles.replyHeading1 : level === 2 ? styles.replyHeading2 : styles.replyHeading3,
|
||||
]
|
||||
}
|
||||
|
||||
return blocks.map((block, index) =>
|
||||
block.type === "code" ? (
|
||||
<View key={`${keyPrefix}-code-${index}`} style={styles.readerCodeBlock}>
|
||||
{block.language ? <Text style={styles.readerCodeLanguage}>{block.language}</Text> : null}
|
||||
<Text style={styles.readerCodeText}>{block.content}</Text>
|
||||
</View>
|
||||
) : block.type === "heading" ? (
|
||||
<Text key={`${keyPrefix}-heading-${index}`} style={getHeadingStyle(block.level)}>
|
||||
{renderMarkdownInline(block.content, index)}
|
||||
</Text>
|
||||
) : (
|
||||
<Text key={`${keyPrefix}-text-${index}`} style={paragraphStyle}>
|
||||
{renderMarkdownInline(block.content, index)}
|
||||
</Text>
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
const renderTranscriptionPanel = () => (
|
||||
<View style={styles.transcriptionPanel} onLayout={handleTranscriptionPanelLayout}>
|
||||
<View style={styles.transcriptionTopActions} pointerEvents="box-none">
|
||||
<Pressable
|
||||
onPress={handleOpenWhisperSettings}
|
||||
style={({ pressed }) => [styles.clearButton, pressed && styles.clearButtonPressed]}
|
||||
hitSlop={8}
|
||||
>
|
||||
<SymbolView
|
||||
name={{ ios: "gearshape.fill", android: "settings", web: "settings" }}
|
||||
size={18}
|
||||
weight="semibold"
|
||||
tintColor={C.textTertiary}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={handleClearTranscript}
|
||||
style={({ pressed }) => [styles.clearButton, pressed && styles.clearButtonPressed]}
|
||||
hitSlop={8}
|
||||
>
|
||||
<Animated.View style={animatedClearIconStyle}>
|
||||
<SymbolView
|
||||
name={{ ios: "arrow.counterclockwise", android: "refresh", web: "refresh" }}
|
||||
size={18}
|
||||
weight="semibold"
|
||||
tintColor="#A0A0A0"
|
||||
/>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{whisperError ? (
|
||||
<View style={styles.modelErrorBadge}>
|
||||
<Text style={styles.modelErrorText}>{whisperError}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{promptPagerData.length > 1 ? (
|
||||
<FlatList
|
||||
ref={promptPagerRef}
|
||||
data={promptPagerData}
|
||||
keyExtractor={promptPagerKeyExtractor}
|
||||
horizontal
|
||||
pagingEnabled
|
||||
bounces={false}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
onMomentumScrollEnd={handlePromptPagerSnap}
|
||||
initialScrollIndex={0}
|
||||
getItemLayout={(_data, index) => ({
|
||||
length: pagerPageWidth,
|
||||
offset: pagerPageWidth * index,
|
||||
index,
|
||||
})}
|
||||
style={styles.transcriptionScroll}
|
||||
renderItem={({ item }) =>
|
||||
item === "live" ? (
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
style={{ width: pagerPageWidth }}
|
||||
contentContainerStyle={[styles.transcriptionContent, styles.transcriptionContentLive]}
|
||||
onContentSizeChange={() => scrollViewRef.current?.scrollToEnd({ animated: true })}
|
||||
>
|
||||
<Animated.View style={animatedTranscriptSendStyle}>
|
||||
{displayedTranscript ? (
|
||||
<Text style={styles.transcriptionText}>{displayedTranscript}</Text>
|
||||
) : isSending ? null : (
|
||||
<Text style={styles.placeholderText}>Your transcription will appear here…</Text>
|
||||
)}
|
||||
</Animated.View>
|
||||
<Animated.View style={[styles.swipeHint, animatedSwipeHintStyle]} pointerEvents="none">
|
||||
{!displayedTranscript && !isSending ? (
|
||||
<>
|
||||
<Text style={styles.swipeHintText}>Swipe left to see previous prompts</Text>
|
||||
<Text style={styles.swipeHintArrow}>→</Text>
|
||||
</>
|
||||
) : null}
|
||||
</Animated.View>
|
||||
</ScrollView>
|
||||
) : (
|
||||
<ScrollView style={{ width: pagerPageWidth }} contentContainerStyle={styles.transcriptionContent}>
|
||||
<Text style={styles.promptHistoryLabel}>Previous prompt</Text>
|
||||
<Text style={styles.promptHistoryText}>{item.promptText}</Text>
|
||||
</ScrollView>
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
style={styles.transcriptionScroll}
|
||||
contentContainerStyle={styles.transcriptionContent}
|
||||
onContentSizeChange={() => scrollViewRef.current?.scrollToEnd({ animated: true })}
|
||||
>
|
||||
<Animated.View style={animatedTranscriptSendStyle}>
|
||||
{displayedTranscript ? (
|
||||
<Text style={styles.transcriptionText}>{displayedTranscript}</Text>
|
||||
) : isSending ? null : (
|
||||
<Text style={styles.placeholderText}>Your transcription will appear here…</Text>
|
||||
)}
|
||||
</Animated.View>
|
||||
</ScrollView>
|
||||
)}
|
||||
|
||||
<Animated.View
|
||||
style={[styles.waveformBoxesRow, animatedWaveformRowStyle]}
|
||||
pointerEvents="none"
|
||||
onLayout={handleWaveformLayout}
|
||||
>
|
||||
{Array.from({ length: WAVEFORM_ROWS }).map((_, row) => (
|
||||
<View key={`row-${row}`} style={styles.waveformGridRow}>
|
||||
{waveformLevels.map((_, col) => (
|
||||
<View key={`cell-${row}-${col}`} style={[styles.waveformBox, getWaveformCellStyle(row, col)]} />
|
||||
))}
|
||||
</View>
|
||||
))}
|
||||
</Animated.View>
|
||||
</View>
|
||||
)
|
||||
|
||||
const toggleServerMenu = useCallback(() => {
|
||||
void Haptics.selectionAsync().catch(() => {})
|
||||
setDropdownMode((prev) => {
|
||||
const next = prev === "server" ? "none" : "server"
|
||||
if (next === "server") {
|
||||
setDropdownRenderMode("server")
|
||||
}
|
||||
if (next === "server") {
|
||||
refreshAllServerHealth()
|
||||
refreshDiscovery()
|
||||
}
|
||||
return next
|
||||
})
|
||||
}, [refreshAllServerHealth, refreshDiscovery])
|
||||
|
||||
const toggleSessionMenu = useCallback(() => {
|
||||
if (!activeServer || activeServer.status !== "online") return
|
||||
void Haptics.selectionAsync().catch(() => {})
|
||||
void refreshServerStatusAndSessions(activeServer.id)
|
||||
setDropdownRenderMode("session")
|
||||
setDropdownMode((prev) => (prev === "session" ? "none" : "session"))
|
||||
}, [activeServer, refreshServerStatusAndSessions])
|
||||
|
||||
const handleSelectServer = useCallback(
|
||||
(id: string) => {
|
||||
selectServer(id)
|
||||
setDropdownMode("none")
|
||||
void refreshServerStatusAndSessions(id)
|
||||
},
|
||||
[refreshServerStatusAndSessions, selectServer],
|
||||
)
|
||||
|
||||
const handleSelectSession = useCallback(
|
||||
(id: string) => {
|
||||
selectSession(id)
|
||||
setDropdownMode("none")
|
||||
},
|
||||
[selectSession],
|
||||
)
|
||||
|
||||
const handleCreateRootSession = useCallback(() => {
|
||||
if (!activeServer || activeServer.status !== "online" || isCreatingSession) {
|
||||
return
|
||||
}
|
||||
|
||||
setSessionCreateMode("root")
|
||||
void createSession(activeServer.id)
|
||||
.then((created) => {
|
||||
if (!created) {
|
||||
Alert.alert("Could not create session", "Please check that your server is online and try again.")
|
||||
return
|
||||
}
|
||||
|
||||
setDropdownMode("none")
|
||||
})
|
||||
.finally(() => {
|
||||
setSessionCreateMode(null)
|
||||
})
|
||||
}, [activeServer, createSession, isCreatingSession])
|
||||
|
||||
const handleCreateSessionLikeCurrent = useCallback(() => {
|
||||
if (!activeServer || activeServer.status !== "online" || !activeSession || isCreatingSession) {
|
||||
return
|
||||
}
|
||||
|
||||
setSessionCreateMode("same")
|
||||
void createSession(activeServer.id, {
|
||||
directory: activeSession.directory,
|
||||
workspaceID: activeSession.workspaceID,
|
||||
})
|
||||
.then((created) => {
|
||||
if (!created) {
|
||||
Alert.alert("Could not create session", "Please check that your server is online and try again.")
|
||||
return
|
||||
}
|
||||
|
||||
setDropdownMode("none")
|
||||
})
|
||||
.finally(() => {
|
||||
setSessionCreateMode(null)
|
||||
})
|
||||
}, [activeServer, activeSession, createSession, isCreatingSession])
|
||||
|
||||
const handleDeleteServer = useCallback(
|
||||
(id: string) => {
|
||||
const server = serversRef.current.find((s) => s.id === id)
|
||||
if (server && devicePushToken && server.relaySecret.trim().length > 0) {
|
||||
unregisterRelayDevice({
|
||||
relayBaseURL: server.relayURL,
|
||||
secret: server.relaySecret.trim(),
|
||||
deviceToken: devicePushToken,
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
removeServer(id)
|
||||
},
|
||||
[devicePushToken, removeServer, serversRef],
|
||||
)
|
||||
|
||||
const handleConnectDiscoveredServer = useCallback(
|
||||
(url: string) => {
|
||||
const ok = addServer(url, DEFAULT_RELAY_URL, "")
|
||||
if (!ok) {
|
||||
Alert.alert("Could not add server", "The discovered server could not be added. Try scanning the QR code.")
|
||||
return
|
||||
}
|
||||
|
||||
setDropdownMode("none")
|
||||
void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {})
|
||||
},
|
||||
[addServer],
|
||||
)
|
||||
|
||||
const handleStartScan = useCallback(async () => {
|
||||
scanLockRef.current = false
|
||||
const current =
|
||||
camera ??
|
||||
(() => {
|
||||
try {
|
||||
// Expo dev builds were failing to resolve this native module through async import().
|
||||
const mod = require("expo-camera") as typeof import("expo-camera") & {
|
||||
Camera?: { requestCameraPermissionsAsync?: unknown }
|
||||
}
|
||||
const direct = (mod as { requestCameraPermissionsAsync?: unknown }).requestCameraPermissionsAsync
|
||||
const fromCamera = mod.Camera?.requestCameraPermissionsAsync
|
||||
let requestCameraPermissionsAsync: (() => Promise<{ granted: boolean }>) | null = null
|
||||
if (typeof direct === "function") {
|
||||
requestCameraPermissionsAsync = direct as () => Promise<{ granted: boolean }>
|
||||
} else if (typeof fromCamera === "function") {
|
||||
requestCameraPermissionsAsync = fromCamera as () => Promise<{ granted: boolean }>
|
||||
}
|
||||
|
||||
if (!mod.CameraView || !requestCameraPermissionsAsync) {
|
||||
return null
|
||||
}
|
||||
|
||||
const next = {
|
||||
CameraView: mod.CameraView,
|
||||
requestCameraPermissionsAsync,
|
||||
}
|
||||
setCamera(next)
|
||||
return next
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})()
|
||||
if (!current) {
|
||||
Alert.alert("Scanner unavailable", "This build does not include camera support. Reinstall the latest dev build.")
|
||||
return
|
||||
}
|
||||
if (camGranted) {
|
||||
setScanOpen(true)
|
||||
return
|
||||
}
|
||||
const res = await current.requestCameraPermissionsAsync()
|
||||
if (!res.granted) return
|
||||
setCamGranted(true)
|
||||
setScanOpen(true)
|
||||
}, [camGranted, camera])
|
||||
|
||||
const completeOnboarding = useCallback(
|
||||
(openScanner: boolean) => {
|
||||
setOnboardingComplete(true)
|
||||
void FileSystem.writeAsStringAsync(ONBOARDING_STATE_FILE, JSON.stringify({ completed: true })).catch(() => {})
|
||||
|
||||
if (openScanner) {
|
||||
void handleStartScan()
|
||||
}
|
||||
},
|
||||
[handleStartScan],
|
||||
)
|
||||
|
||||
const handleReplayOnboarding = useCallback(() => {
|
||||
setWhisperSettingsOpen(false)
|
||||
setScanOpen(false)
|
||||
setPairSelectionOpen(false)
|
||||
setPendingPair(null)
|
||||
setPairHostOptions([])
|
||||
setPairHostProbes({})
|
||||
setSelectedPairHostURL(null)
|
||||
setIsConnectingPairHost(false)
|
||||
setDropdownMode("none")
|
||||
setOnboardingStep(0)
|
||||
setMicrophonePermissionState(permissionGranted ? "granted" : "idle")
|
||||
setNotificationPermissionState("idle")
|
||||
setLocalNetworkPermissionState("idle")
|
||||
setOnboardingReady(true)
|
||||
setOnboardingComplete(false)
|
||||
void FileSystem.deleteAsync(ONBOARDING_STATE_FILE, { idempotent: true }).catch(() => {})
|
||||
}, [permissionGranted])
|
||||
|
||||
const closePairSelection = useCallback(() => {
|
||||
setPairSelectionOpen(false)
|
||||
setPendingPair(null)
|
||||
setPairHostOptions([])
|
||||
setPairHostProbes({})
|
||||
setSelectedPairHostURL(null)
|
||||
setIsConnectingPairHost(false)
|
||||
pairProbeRunRef.current += 1
|
||||
}, [])
|
||||
|
||||
const handleConnectSelectedPairHost = useCallback(() => {
|
||||
if (!pendingPair || !selectedPairHostURL || isConnectingPairHost) {
|
||||
return
|
||||
}
|
||||
|
||||
setIsConnectingPairHost(true)
|
||||
const ok = addServer(selectedPairHostURL, pendingPair.relayURL, pendingPair.relaySecret, pendingPair.serverID)
|
||||
|
||||
if (!ok) {
|
||||
Alert.alert("Could not add server", "The selected host could not be added. Try another host.")
|
||||
setIsConnectingPairHost(false)
|
||||
return
|
||||
}
|
||||
|
||||
closePairSelection()
|
||||
void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {})
|
||||
}, [addServer, closePairSelection, isConnectingPairHost, pendingPair, selectedPairHostURL])
|
||||
|
||||
const handleRescanFromPairSelection = useCallback(() => {
|
||||
closePairSelection()
|
||||
scanLockRef.current = false
|
||||
void handleStartScan()
|
||||
}, [closePairSelection, handleStartScan])
|
||||
|
||||
useEffect(() => {
|
||||
if (latestAssistantResponse.trim().length === 0 || activePermissionRequest !== null) {
|
||||
setReaderModeOpen(false)
|
||||
}
|
||||
}, [activePermissionRequest, latestAssistantResponse])
|
||||
|
||||
const connectPairPayload = useCallback((rawData: string, source: "scan" | "link") => {
|
||||
const fromScan = source === "scan"
|
||||
if (fromScan && scanLockRef.current) return
|
||||
|
||||
if (fromScan) {
|
||||
scanLockRef.current = true
|
||||
}
|
||||
|
||||
const pair = parsePair(rawData)
|
||||
if (!pair) {
|
||||
if (fromScan) {
|
||||
void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {})
|
||||
setTimeout(() => {
|
||||
scanLockRef.current = false
|
||||
}, 750)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const options = normalizePairHosts(pair.hosts)
|
||||
if (!options.length) {
|
||||
if (fromScan) {
|
||||
scanLockRef.current = false
|
||||
}
|
||||
Alert.alert("No valid hosts found", "The QR payload did not include any valid server hosts.")
|
||||
return
|
||||
}
|
||||
|
||||
if (fromScan) {
|
||||
setScanOpen(false)
|
||||
}
|
||||
|
||||
setPendingPair(pair)
|
||||
setPairHostOptions(options)
|
||||
setSelectedPairHostURL(options[0]?.url ?? null)
|
||||
setPairHostProbes(Object.fromEntries(options.map((item) => [item.url, { status: "checking" as const }])))
|
||||
setPairSelectionOpen(true)
|
||||
|
||||
if (fromScan) {
|
||||
scanLockRef.current = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleScan = useCallback(
|
||||
(event: Scan) => {
|
||||
connectPairPayload(event.data, "scan")
|
||||
},
|
||||
[connectPairPayload],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (scanOpen) return
|
||||
scanLockRef.current = false
|
||||
}, [scanOpen])
|
||||
|
||||
useEffect(() => {
|
||||
if (!pairSelectionOpen || !pairHostOptions.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const runID = pairProbeRunRef.current + 1
|
||||
pairProbeRunRef.current = runID
|
||||
|
||||
setPairHostProbes((prev) => {
|
||||
const next: Record<string, PairHostProbe> = {}
|
||||
for (const option of pairHostOptions) {
|
||||
next[option.url] = prev[option.url]?.status === "online" ? prev[option.url] : { status: "checking" }
|
||||
}
|
||||
return next
|
||||
})
|
||||
|
||||
pairHostOptions.forEach((option) => {
|
||||
void (async () => {
|
||||
const controller = new AbortController()
|
||||
const timeout = setTimeout(() => controller.abort(), 2800)
|
||||
const startedAt = Date.now()
|
||||
|
||||
try {
|
||||
const response = await fetch(`${option.url}/health`, {
|
||||
method: "GET",
|
||||
signal: controller.signal,
|
||||
})
|
||||
if (pairProbeRunRef.current !== runID) return
|
||||
|
||||
if (response.ok) {
|
||||
setPairHostProbes((prev) => ({
|
||||
...prev,
|
||||
[option.url]: {
|
||||
status: "online",
|
||||
latencyMs: Math.max(1, Date.now() - startedAt),
|
||||
},
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
setPairHostProbes((prev) => ({
|
||||
...prev,
|
||||
[option.url]: {
|
||||
status: "offline",
|
||||
note: `HTTP ${response.status}`,
|
||||
},
|
||||
}))
|
||||
} catch (err) {
|
||||
if (pairProbeRunRef.current !== runID) return
|
||||
|
||||
const aborted = err instanceof Error && err.name === "AbortError"
|
||||
let note = aborted ? "Timed out" : "Unavailable"
|
||||
if (!aborted) {
|
||||
try {
|
||||
const parsed = new URL(option.url)
|
||||
if (Platform.OS === "ios" && parsed.protocol === "http:" && !looksLikeLocalHost(parsed.hostname)) {
|
||||
note = "ATS blocked"
|
||||
}
|
||||
} catch {
|
||||
// ignore parse failure and keep default note
|
||||
}
|
||||
}
|
||||
|
||||
setPairHostProbes((prev) => ({
|
||||
...prev,
|
||||
[option.url]: {
|
||||
status: "offline",
|
||||
note,
|
||||
},
|
||||
}))
|
||||
} finally {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
})()
|
||||
})
|
||||
}, [pairHostOptions, pairSelectionOpen])
|
||||
|
||||
useEffect(() => {
|
||||
let active = true
|
||||
|
||||
const handleURL = async (url: string | null) => {
|
||||
if (!url) return
|
||||
if (!parsePair(url)) return
|
||||
|
||||
if (!restoredRef.current) {
|
||||
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
if (restoredRef.current || !active) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!active) return
|
||||
connectPairPayload(url, "link")
|
||||
}
|
||||
|
||||
void Linking.getInitialURL()
|
||||
.then((url) => handleURL(url))
|
||||
.catch(() => {})
|
||||
|
||||
const sub = Linking.addEventListener("url", (event) => {
|
||||
void handleURL(event.url)
|
||||
})
|
||||
|
||||
return () => {
|
||||
active = false
|
||||
sub.remove()
|
||||
}
|
||||
}, [connectPairPayload, restoredRef])
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeServerId) return
|
||||
void refreshServerStatusAndSessions(activeServerId)
|
||||
const timer = setInterval(() => {
|
||||
void refreshServerStatusAndSessions(activeServerId)
|
||||
}, 15000)
|
||||
return () => clearInterval(timer)
|
||||
}, [activeServerId, refreshServerStatusAndSessions])
|
||||
|
||||
const defaultModelInstalled = installedWhisperModels.includes(defaultWhisperModel)
|
||||
let onboardingProgressRaw = 0
|
||||
if (downloadingModelID) {
|
||||
onboardingProgressRaw = downloadProgress
|
||||
} else if (defaultModelInstalled || activeWhisperModel === defaultWhisperModel) {
|
||||
onboardingProgressRaw = 1
|
||||
} else if (isPreparingWhisperModel) {
|
||||
onboardingProgressRaw = 0.12
|
||||
}
|
||||
const onboardingProgress = Math.max(0, Math.min(1, onboardingProgressRaw))
|
||||
const onboardingProgressPct = Math.round(onboardingProgress * 100)
|
||||
let onboardingModelStatus = "Downloading model in background"
|
||||
if (downloadingModelID) {
|
||||
onboardingModelStatus = `Downloading model in background ${onboardingProgressPct}%`
|
||||
} else if (onboardingProgress >= 1) {
|
||||
onboardingModelStatus = "Model ready in background"
|
||||
}
|
||||
const onboardingSteps = [
|
||||
{
|
||||
title: "Microphone access.",
|
||||
body: "Control only listens while you hold the record button.",
|
||||
primaryLabel: microphonePermissionState === "pending" ? "Requesting microphone access..." : "Continue",
|
||||
primaryDisabled: microphonePermissionState === "pending",
|
||||
secondaryLabel: undefined,
|
||||
visualTag: "MIC",
|
||||
visualSurfaceStyle: styles.onboardingVisualSurfaceMic,
|
||||
visualOrbStyle: styles.onboardingVisualOrbMic,
|
||||
visualTagStyle: styles.onboardingVisualTagMic,
|
||||
},
|
||||
{
|
||||
title: "Turn on notifications.",
|
||||
body: "Get alerts when your OpenCode run finishes, fails, or needs your attention.",
|
||||
primaryLabel: notificationPermissionState === "pending" ? "Requesting notification access..." : "Continue",
|
||||
primaryDisabled: notificationPermissionState === "pending",
|
||||
secondaryLabel: undefined,
|
||||
visualTag: "PUSH",
|
||||
visualSurfaceStyle: styles.onboardingVisualSurfaceNotifications,
|
||||
visualOrbStyle: styles.onboardingVisualOrbNotifications,
|
||||
visualTagStyle: styles.onboardingVisualTagNotifications,
|
||||
},
|
||||
{
|
||||
title: "Local network access.",
|
||||
body: "This lets Control discover your machine on the same network.",
|
||||
primaryLabel: localNetworkPermissionState === "pending" ? "Requesting local network access..." : "Continue",
|
||||
primaryDisabled: localNetworkPermissionState === "pending",
|
||||
secondaryLabel: undefined,
|
||||
visualTag: "LAN",
|
||||
visualSurfaceStyle: styles.onboardingVisualSurfaceNetwork,
|
||||
visualOrbStyle: styles.onboardingVisualOrbNetwork,
|
||||
visualTagStyle: styles.onboardingVisualTagNetwork,
|
||||
},
|
||||
{
|
||||
title: "Pair your computer.",
|
||||
body: "Start `opencode serve --mdns` on your computer. Control can discover nearby servers automatically, or you can scan a QR code.",
|
||||
primaryLabel: "Scan OpenCode QR (optional)",
|
||||
primaryDisabled: false,
|
||||
secondaryLabel: "Skip and use discovery",
|
||||
visualTag: "PAIR",
|
||||
visualSurfaceStyle: styles.onboardingVisualSurfacePair,
|
||||
visualOrbStyle: styles.onboardingVisualOrbPair,
|
||||
visualTagStyle: styles.onboardingVisualTagPair,
|
||||
},
|
||||
] as const
|
||||
const onboardingStepCount = onboardingSteps.length
|
||||
const clampedOnboardingStep = Math.max(0, Math.min(onboardingStep, onboardingStepCount - 1))
|
||||
const onboardingCurrentStep = onboardingSteps[clampedOnboardingStep]
|
||||
const {
|
||||
title: onboardingTitle,
|
||||
body: onboardingBody,
|
||||
primaryLabel: onboardingPrimaryLabel,
|
||||
primaryDisabled: onboardingPrimaryDisabled,
|
||||
secondaryLabel: onboardingSecondaryLabel,
|
||||
visualTag: onboardingVisualTag,
|
||||
visualSurfaceStyle: onboardingVisualSurfaceStyle,
|
||||
visualOrbStyle: onboardingVisualOrbStyle,
|
||||
visualTagStyle: onboardingVisualTagStyle,
|
||||
} = onboardingCurrentStep
|
||||
const onboardingSafeStyle = useMemo(
|
||||
() => [styles.onboardingRoot, { paddingTop: insets.top + 8, paddingBottom: Math.max(insets.bottom, 16) }],
|
||||
[insets.bottom, insets.top],
|
||||
)
|
||||
|
||||
if (!onboardingReady) {
|
||||
return (
|
||||
<SafeAreaView style={onboardingSafeStyle} edges={["left", "right"]}>
|
||||
<StatusBar style="light" />
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
||||
if (!onboardingComplete) {
|
||||
return (
|
||||
<SafeAreaView style={onboardingSafeStyle} edges={["left", "right"]}>
|
||||
<StatusBar style="light" />
|
||||
|
||||
<View style={styles.onboardingShell}>
|
||||
<View style={styles.onboardingTopRail}>
|
||||
<View style={styles.onboardingModelRow}>
|
||||
<Text style={styles.onboardingModelText}>{onboardingModelStatus}</Text>
|
||||
<View style={styles.onboardingModelTrack}>
|
||||
<View
|
||||
style={[
|
||||
styles.onboardingModelFill,
|
||||
{ width: `${Math.max(onboardingProgressPct, onboardingProgress > 0 ? 6 : 0)}%` },
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.onboardingContent}>
|
||||
<View style={[styles.onboardingVisualSurface, onboardingVisualSurfaceStyle]}>
|
||||
<View style={[styles.onboardingVisualOrb, styles.onboardingVisualOrbOne, onboardingVisualOrbStyle]} />
|
||||
<View style={[styles.onboardingVisualOrb, styles.onboardingVisualOrbTwo, onboardingVisualOrbStyle]} />
|
||||
<View style={[styles.onboardingVisualTag, onboardingVisualTagStyle]}>
|
||||
<Text style={styles.onboardingVisualTagText}>{onboardingVisualTag}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.onboardingCopyBlock}>
|
||||
<Text
|
||||
style={styles.onboardingEyebrow}
|
||||
>{`STEP ${clampedOnboardingStep + 1} OF ${onboardingStepCount}`}</Text>
|
||||
<Text style={styles.onboardingTitle}>{onboardingTitle}</Text>
|
||||
<Text style={styles.onboardingBody}>{onboardingBody}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.onboardingFooter}>
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
if (clampedOnboardingStep === 0) {
|
||||
void (async () => {
|
||||
await handleRequestMicrophonePermission()
|
||||
setOnboardingStep(1)
|
||||
})()
|
||||
return
|
||||
}
|
||||
|
||||
if (clampedOnboardingStep === 1) {
|
||||
void (async () => {
|
||||
await handleRequestNotificationPermission()
|
||||
setOnboardingStep(2)
|
||||
})()
|
||||
return
|
||||
}
|
||||
|
||||
if (clampedOnboardingStep === 2) {
|
||||
void (async () => {
|
||||
await handleRequestLocalNetworkPermission()
|
||||
setOnboardingStep(3)
|
||||
})()
|
||||
return
|
||||
}
|
||||
|
||||
completeOnboarding(true)
|
||||
}}
|
||||
style={({ pressed }) => [
|
||||
styles.onboardingPrimaryButton,
|
||||
onboardingPrimaryDisabled && styles.onboardingPrimaryButtonDisabled,
|
||||
pressed && styles.clearButtonPressed,
|
||||
]}
|
||||
disabled={onboardingPrimaryDisabled}
|
||||
>
|
||||
<Text style={styles.onboardingPrimaryButtonText}>{onboardingPrimaryLabel}</Text>
|
||||
<SymbolView
|
||||
name={{ ios: "arrow.right", android: "arrow_forward", web: "arrow_forward" }}
|
||||
size={20}
|
||||
weight="semibold"
|
||||
tintColor="#FFFFFF"
|
||||
/>
|
||||
</Pressable>
|
||||
|
||||
{onboardingSecondaryLabel ? (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
if (clampedOnboardingStep < onboardingStepCount - 1) {
|
||||
setOnboardingStep((step) => Math.min(step + 1, onboardingStepCount - 1))
|
||||
return
|
||||
}
|
||||
|
||||
completeOnboarding(false)
|
||||
}}
|
||||
style={({ pressed }) => [styles.onboardingSecondaryButton, pressed && styles.clearButtonPressed]}
|
||||
>
|
||||
<Text style={styles.onboardingSecondaryText}>{onboardingSecondaryLabel}</Text>
|
||||
</Pressable>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<StatusBar style="light" />
|
||||
|
||||
{isDropdownOpen ? <Pressable style={styles.dismissOverlay} onPress={closeDropdown} /> : null}
|
||||
|
||||
{/* Workspace header */}
|
||||
<View style={styles.headerAnchor}>
|
||||
<Animated.View style={[styles.statusBar, animatedHeaderStyle, animatedHeaderShadowStyle]}>
|
||||
{activeServer ? (
|
||||
<View style={styles.headerSplitRow}>
|
||||
<Pressable
|
||||
onPress={toggleServerMenu}
|
||||
style={({ pressed }) => [styles.headerSplitLeft, pressed && styles.clearButtonPressed]}
|
||||
>
|
||||
<View style={styles.headerServerLabel}>
|
||||
<View style={styles.headerStatusIconWrap}>
|
||||
<View style={[styles.serverStatusDot, headerDotStyle]} />
|
||||
</View>
|
||||
<Text
|
||||
style={[styles.workspaceHeaderText, styles.headerServerText]}
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
{activeServer.name}
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
|
||||
<View style={styles.headerSplitDivider} />
|
||||
|
||||
<Pressable
|
||||
onPress={toggleSessionMenu}
|
||||
style={({ pressed }) => [styles.headerSplitRight, pressed && styles.clearButtonPressed]}
|
||||
>
|
||||
<Text
|
||||
style={[styles.workspaceHeaderText, styles.headerSessionText]}
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
{activeSession?.title ?? "Select session"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
) : (
|
||||
<Pressable
|
||||
onPress={toggleServerMenu}
|
||||
style={({ pressed }) => [styles.statusBarTapArea, pressed && styles.clearButtonPressed]}
|
||||
>
|
||||
<View style={styles.headerServerLabel}>
|
||||
<View style={styles.headerStatusIconWrap}>
|
||||
<View style={[styles.serverStatusDot, headerDotStyle]} />
|
||||
</View>
|
||||
<Text style={styles.workspaceHeaderText}>{headerTitle}</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
)}
|
||||
|
||||
<Animated.View
|
||||
style={[styles.serverMenuInline, animatedServerMenuStyle]}
|
||||
pointerEvents={isDropdownOpen ? "auto" : "none"}
|
||||
>
|
||||
<ScrollView
|
||||
style={styles.dropdownListViewport}
|
||||
contentContainerStyle={styles.dropdownListContent}
|
||||
showsVerticalScrollIndicator={false}
|
||||
bounces={false}
|
||||
>
|
||||
{effectiveDropdownMode === "server" ? (
|
||||
<View onLayout={handleServerMenuListLayout}>
|
||||
<Text style={styles.serverGroupLabel}>Saved:</Text>
|
||||
|
||||
{servers.length === 0 ? (
|
||||
<Text style={[styles.serverEmptyText, styles.serverGroupEmptyText]}>No saved servers</Text>
|
||||
) : (
|
||||
servers.map((server) => (
|
||||
<Pressable
|
||||
key={server.id}
|
||||
onPress={() => handleSelectServer(server.id)}
|
||||
style={({ pressed }) => [styles.serverRow, pressed && styles.serverRowPressed]}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.serverStatusDot,
|
||||
server.status === "online" ? styles.serverStatusActive : styles.serverStatusOffline,
|
||||
]}
|
||||
/>
|
||||
<Text style={styles.serverNameText}>{server.name}</Text>
|
||||
<Pressable onPress={() => handleDeleteServer(server.id)} hitSlop={8}>
|
||||
<SymbolView
|
||||
name={{ ios: "xmark", android: "close", web: "close" }}
|
||||
size={12}
|
||||
weight="bold"
|
||||
tintColor="#8C93A3"
|
||||
/>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
))
|
||||
)}
|
||||
|
||||
<View style={styles.serverGroupHeaderRow}>
|
||||
<Text style={styles.serverGroupLabel}>Discovered:</Text>
|
||||
{discoveryStatus === "scanning" ? <ActivityIndicator size="small" color="#8790A3" /> : null}
|
||||
</View>
|
||||
|
||||
{!discoveryAvailable ? (
|
||||
<Text style={[styles.serverEmptyText, styles.serverGroupEmptyText]}>
|
||||
Discovery unavailable in this build
|
||||
</Text>
|
||||
) : discoveredServerOptions.length === 0 ? (
|
||||
<Text style={[styles.serverEmptyText, styles.serverGroupEmptyText]}>
|
||||
{discoveredServerEmptyLabel}
|
||||
</Text>
|
||||
) : (
|
||||
discoveredServerOptions.map((server, index) => (
|
||||
<Pressable
|
||||
key={server.id}
|
||||
onPress={() => handleConnectDiscoveredServer(server.url)}
|
||||
style={({ pressed }) => [
|
||||
styles.serverRow,
|
||||
index === discoveredServerOptions.length - 1 && styles.serverRowLast,
|
||||
pressed && styles.serverRowPressed,
|
||||
]}
|
||||
>
|
||||
<View style={[styles.serverStatusDot, styles.serverStatusChecking]} />
|
||||
<View style={styles.discoveredServerCopy}>
|
||||
<Text style={styles.serverNameText} numberOfLines={1}>
|
||||
{server.name}
|
||||
</Text>
|
||||
<Text style={styles.discoveredServerMeta} numberOfLines={1} ellipsizeMode="middle">
|
||||
{server.url}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.discoveredServerAction}>Connect</Text>
|
||||
</Pressable>
|
||||
))
|
||||
)}
|
||||
|
||||
{discoveryStatus === "error" && discoveryError ? (
|
||||
<Text style={styles.discoveryErrorText} numberOfLines={1} ellipsizeMode="tail">
|
||||
{discoveryError}
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
) : activeServer ? (
|
||||
<View onLayout={handleSessionMenuListLayout}>
|
||||
{activeSession ? (
|
||||
<>
|
||||
<View style={styles.currentSessionSummary}>
|
||||
<Text style={styles.currentSessionLabel}>Current session</Text>
|
||||
|
||||
<View style={styles.currentSessionMetaRow}>
|
||||
<Text style={styles.currentSessionMetaKey}>Working dir</Text>
|
||||
<Text style={styles.currentSessionMetaValue} numberOfLines={1} ellipsizeMode="middle">
|
||||
{formatWorkingDirectory(currentSessionDirectory)}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.currentSessionMetaRow}>
|
||||
<Text style={styles.currentSessionMetaKey}>Model</Text>
|
||||
<Text style={styles.currentSessionMetaValue} numberOfLines={1} ellipsizeMode="middle">
|
||||
{currentSessionModelLabel}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.currentSessionMetaRow}>
|
||||
<Text style={styles.currentSessionMetaKey}>Updated</Text>
|
||||
<Text style={styles.currentSessionMetaValue}>{currentSessionUpdated || "Just now"}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.currentSessionDivider} />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{sessionList.length === 0 ? (
|
||||
activeServer.sessionsLoading ? null : (
|
||||
<Text style={styles.serverEmptyText}>
|
||||
{activeSession ? "No other sessions available" : "No sessions available"}
|
||||
</Text>
|
||||
)
|
||||
) : (
|
||||
sessionList.map((session, index) => (
|
||||
<Pressable
|
||||
key={session.id}
|
||||
onPress={() => handleSelectSession(session.id)}
|
||||
style={({ pressed }) => [
|
||||
styles.serverRow,
|
||||
index === sessionList.length - 1 && styles.serverRowLast,
|
||||
pressed && styles.serverRowPressed,
|
||||
]}
|
||||
>
|
||||
<View style={[styles.serverStatusDot, styles.serverStatusActive]} />
|
||||
<Text style={styles.serverNameText} numberOfLines={1}>
|
||||
{session.title}
|
||||
</Text>
|
||||
<Text style={styles.sessionUpdatedText}>{formatSessionUpdated(session.updated)}</Text>
|
||||
</Pressable>
|
||||
))
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<Text style={styles.serverEmptyText}>Select a server first</Text>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
{effectiveDropdownMode === "server" ? (
|
||||
<View onLayout={handleServerMenuFooterLayout}>
|
||||
<Pressable onPress={() => void handleStartScan()} style={styles.addServerButton}>
|
||||
<Text style={styles.addServerButtonText}>Add server by scanning QR code</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
) : effectiveDropdownMode === "session" && activeServer?.status === "online" ? (
|
||||
<View style={styles.sessionMenuActions} onLayout={handleSessionMenuFooterLayout}>
|
||||
{activeSession ? (
|
||||
<Pressable
|
||||
onPress={handleCreateSessionLikeCurrent}
|
||||
disabled={isCreatingSession}
|
||||
style={({ pressed }) => [
|
||||
styles.serverRow,
|
||||
styles.sessionMenuActionRow,
|
||||
isCreatingSession && styles.sessionMenuActionButtonDisabled,
|
||||
pressed && styles.clearButtonPressed,
|
||||
]}
|
||||
>
|
||||
<View style={styles.sessionMenuActionInner}>
|
||||
<View style={styles.sessionMenuActionIconSlot}>
|
||||
<SymbolView
|
||||
name={{ ios: "folder.badge.plus", android: "create_new_folder", web: "create_new_folder" }}
|
||||
size={12}
|
||||
tintColor="#9BA3B5"
|
||||
/>
|
||||
</View>
|
||||
<Text style={styles.sessionMenuActionText}>
|
||||
{sessionCreateMode === "same" ? "Creating workspace session..." : "New session with workspace"}
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
) : null}
|
||||
|
||||
<Pressable
|
||||
onPress={handleCreateRootSession}
|
||||
disabled={isCreatingSession}
|
||||
style={({ pressed }) => [
|
||||
styles.serverRow,
|
||||
styles.sessionMenuActionRow,
|
||||
styles.serverRowLast,
|
||||
isCreatingSession && styles.sessionMenuActionButtonDisabled,
|
||||
pressed && styles.clearButtonPressed,
|
||||
]}
|
||||
>
|
||||
<View style={styles.sessionMenuActionInner}>
|
||||
<View style={styles.sessionMenuActionIconSlot}>
|
||||
<SymbolView name={{ ios: "plus", android: "add", web: "add" }} size={12} tintColor="#9BA3B5" />
|
||||
</View>
|
||||
<Text style={styles.sessionMenuActionText}>
|
||||
{sessionCreateMode === "root" ? "Creating new session..." : "New session"}
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
) : null}
|
||||
</Animated.View>
|
||||
</Animated.View>
|
||||
</View>
|
||||
|
||||
{/* Transcription area */}
|
||||
<View style={styles.transcriptionArea} onLayout={handleTranscriptionAreaLayout}>
|
||||
{hasPendingPermission && activePermissionCard ? (
|
||||
<View style={[styles.splitCard, styles.permissionCard]}>
|
||||
<View style={styles.permissionHeaderRow}>
|
||||
<View style={styles.permissionStatusDot} />
|
||||
<View style={styles.permissionHeaderCopy}>
|
||||
<Text style={styles.replyCardLabel}>Permission</Text>
|
||||
<Text style={styles.permissionStatusText}>
|
||||
{isReplyingToActivePermission
|
||||
? monitorStatus || "Sending decision…"
|
||||
: pendingPermissionCount > 1
|
||||
? `${pendingPermissionCount} requests pending`
|
||||
: "Action needed"}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ScrollView style={styles.permissionScroll} contentContainerStyle={styles.permissionContent}>
|
||||
<Text style={styles.permissionEyebrow}>{activePermissionCard.eyebrow}</Text>
|
||||
<Text style={styles.permissionTitle}>{activePermissionCard.title}</Text>
|
||||
<Text style={styles.permissionBody}>{activePermissionCard.body}</Text>
|
||||
|
||||
{activePermissionCard.sections.map((section, index) => (
|
||||
<View
|
||||
key={`permission-section-${section.label}-${index}`}
|
||||
style={[
|
||||
styles.permissionSection,
|
||||
index === activePermissionCard.sections.length - 1 && styles.permissionSectionLast,
|
||||
]}
|
||||
>
|
||||
<Text style={styles.permissionSectionLabel}>{section.label}</Text>
|
||||
<Text style={[styles.permissionSectionText, section.mono && styles.permissionSectionTextMono]}>
|
||||
{section.text}
|
||||
</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
|
||||
<View style={styles.permissionFooter}>
|
||||
<Pressable
|
||||
onPress={() => handlePermissionDecision("once")}
|
||||
disabled={isReplyingToActivePermission}
|
||||
style={({ pressed }) => [
|
||||
styles.permissionPrimaryButton,
|
||||
isReplyingToActivePermission && styles.permissionActionDisabled,
|
||||
pressed && styles.clearButtonPressed,
|
||||
]}
|
||||
>
|
||||
{isReplyingToActivePermission ? (
|
||||
<ActivityIndicator color="#FFFFFF" size="small" />
|
||||
) : (
|
||||
<Text style={styles.permissionPrimaryButtonText}>Allow once</Text>
|
||||
)}
|
||||
</Pressable>
|
||||
|
||||
<View style={styles.permissionSecondaryRow}>
|
||||
{activePermissionRequest.always.length > 0 ? (
|
||||
<Pressable
|
||||
onPress={() => handlePermissionDecision("always")}
|
||||
disabled={isReplyingToActivePermission}
|
||||
style={({ pressed }) => [
|
||||
styles.permissionSecondaryButton,
|
||||
isReplyingToActivePermission && styles.permissionActionDisabled,
|
||||
pressed && styles.clearButtonPressed,
|
||||
]}
|
||||
>
|
||||
<Text style={styles.permissionSecondaryButtonText}>Always allow</Text>
|
||||
</Pressable>
|
||||
) : null}
|
||||
|
||||
<Pressable
|
||||
onPress={() => handlePermissionDecision("reject")}
|
||||
disabled={isReplyingToActivePermission}
|
||||
style={({ pressed }) => [
|
||||
styles.permissionRejectButton,
|
||||
activePermissionRequest.always.length === 0 && styles.permissionRejectButtonWide,
|
||||
isReplyingToActivePermission && styles.permissionActionDisabled,
|
||||
pressed && styles.clearButtonPressed,
|
||||
]}
|
||||
>
|
||||
<Text style={styles.permissionRejectButtonText}>Reject</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
) : shouldShowAgentStateCard ? (
|
||||
<>
|
||||
<View style={styles.splitCardStack} pointerEvents={readerModeRendered ? "none" : "auto"}>
|
||||
<View style={[styles.splitCard, styles.replyCard]} onLayout={handleAgentStateCardLayout}>
|
||||
<View style={styles.agentStateHeaderRow}>
|
||||
<View style={styles.agentStateTitleWrap}>
|
||||
<View style={styles.agentStateIconWrap}>
|
||||
{agentStateIcon === "loading" ? (
|
||||
<ActivityIndicator size="small" color="#91A0C0" />
|
||||
) : (
|
||||
<SymbolView
|
||||
name={{ ios: "checkmark.circle.fill", android: "check_circle", web: "check_circle" }}
|
||||
size={16}
|
||||
tintColor={AGENT_SUCCESS_GREEN}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<Text style={styles.replyCardLabel}>Agent</Text>
|
||||
</View>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.agentStateActions,
|
||||
!hasAssistantResponse && styles.agentStateActionsSingle,
|
||||
animatedAgentStateActionsStyle,
|
||||
]}
|
||||
>
|
||||
{hasAssistantResponse ? (
|
||||
<Pressable
|
||||
onPress={handleOpenReaderMode}
|
||||
hitSlop={8}
|
||||
accessibilityLabel="Open Reader"
|
||||
style={styles.agentStateActionButton}
|
||||
>
|
||||
<SymbolView
|
||||
name={READER_OPEN_SYMBOL}
|
||||
size={16}
|
||||
tintColor="#8FA4CC"
|
||||
style={styles.readerToggleSymbol}
|
||||
/>
|
||||
</Pressable>
|
||||
) : null}
|
||||
<Pressable onPress={handleHideAgentState} hitSlop={8} style={styles.agentStateActionButton}>
|
||||
<SymbolView
|
||||
name={{ ios: "xmark", android: "close", web: "close" }}
|
||||
size={14}
|
||||
weight="bold"
|
||||
tintColor="#8D97AB"
|
||||
/>
|
||||
</Pressable>
|
||||
</Animated.View>
|
||||
</View>
|
||||
<ScrollView style={styles.replyScroll} contentContainerStyle={styles.replyContent}>
|
||||
{renderMarkdownBlocks(agentStateBlocks, "reply")}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
{renderTranscriptionPanel()}
|
||||
</View>
|
||||
|
||||
{readerModeVisible ? (
|
||||
<Animated.View
|
||||
pointerEvents={readerModeRendered ? "auto" : "none"}
|
||||
style={[styles.splitCard, styles.readerCard, styles.readerOverlayCard, animatedReaderExpandStyle]}
|
||||
>
|
||||
<View style={styles.readerHeaderRow}>
|
||||
<View style={styles.agentStateTitleWrap}>
|
||||
<View style={styles.agentStateIconWrap}>
|
||||
{agentStateIcon === "loading" ? (
|
||||
<ActivityIndicator size="small" color="#91A0C0" />
|
||||
) : (
|
||||
<SymbolView
|
||||
name={{ ios: "checkmark.circle.fill", android: "check_circle", web: "check_circle" }}
|
||||
size={16}
|
||||
tintColor={AGENT_SUCCESS_GREEN}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<Text style={styles.replyCardLabel}>Agent</Text>
|
||||
</View>
|
||||
<View style={styles.readerActionRail}>
|
||||
<Animated.View style={[styles.readerToggleFloatingAction, animatedReaderToggleTravelStyle]}>
|
||||
<Pressable
|
||||
onPress={handleCloseReaderMode}
|
||||
hitSlop={8}
|
||||
accessibilityLabel="Close Reader"
|
||||
style={styles.agentStateActionButton}
|
||||
>
|
||||
<Animated.View style={[styles.readerToggleIconLayer, animatedReaderToggleOpenIconStyle]}>
|
||||
<SymbolView
|
||||
name={READER_OPEN_SYMBOL}
|
||||
size={16}
|
||||
tintColor="#8FA4CC"
|
||||
style={styles.readerToggleSymbol}
|
||||
/>
|
||||
</Animated.View>
|
||||
<Animated.View style={[styles.readerToggleIconLayer, animatedReaderToggleCloseIconStyle]}>
|
||||
<SymbolView
|
||||
name={READER_CLOSE_SYMBOL}
|
||||
size={16}
|
||||
tintColor="#8FA4CC"
|
||||
style={styles.readerToggleSymbol}
|
||||
/>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
</Animated.View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ScrollView style={styles.readerScroll} contentContainerStyle={styles.readerContent}>
|
||||
{renderMarkdownBlocks(readerBlocks, "reader")}
|
||||
</ScrollView>
|
||||
</Animated.View>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
renderTranscriptionPanel()
|
||||
)}
|
||||
</View>
|
||||
|
||||
{hasPendingPermission ? null : (
|
||||
<View style={styles.controlsRow} onLayout={handleControlsLayout}>
|
||||
<Pressable
|
||||
onPressIn={handlePressIn}
|
||||
onPressOut={handlePressOut}
|
||||
disabled={!permissionGranted || modelLoading}
|
||||
style={[styles.recordPressable, !permissionGranted && styles.recordButtonDisabled]}
|
||||
>
|
||||
<View style={styles.recordButton}>
|
||||
{isTranscribingBulk ? (
|
||||
<View style={styles.recordBusyCenter}>
|
||||
<ActivityIndicator color="#FF2E3F" size="small" />
|
||||
</View>
|
||||
) : modelLoadingState !== "ready" ? (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
styles.loadFill,
|
||||
modelLoadingState === "loading" && styles.loadFillPending,
|
||||
{ width: modelLoadingState === "downloading" ? `${Math.max(pct, 3)}%` : "100%" },
|
||||
]}
|
||||
/>
|
||||
<View style={styles.loadOverlay} pointerEvents="none">
|
||||
<Text style={styles.loadText}>
|
||||
{modelLoadingState === "downloading"
|
||||
? `Downloading ${loadingModelLabel} ${pct}%`
|
||||
: `Loading ${loadingModelLabel}`}
|
||||
</Text>
|
||||
</View>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Animated.View style={[styles.recordBorder, animatedBorderStyle]} pointerEvents="none" />
|
||||
<Animated.View style={[styles.recordDot, animatedDotStyle]} />
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
|
||||
<Animated.View style={[styles.sendSlot, animatedSendStyle]} pointerEvents={shouldShowSend ? "auto" : "none"}>
|
||||
<Pressable
|
||||
onPress={handleSendTranscript}
|
||||
style={({ pressed }) => [
|
||||
styles.sendButton,
|
||||
(isSending || !hasTranscript || !canSendToSession) && styles.sendButtonDisabled,
|
||||
pressed && styles.clearButtonPressed,
|
||||
]}
|
||||
disabled={isSending || !hasTranscript || !canSendToSession}
|
||||
hitSlop={8}
|
||||
>
|
||||
<SymbolView
|
||||
name={{ ios: "arrow.up", android: "arrow_upward", web: "arrow_upward" }}
|
||||
size={28}
|
||||
weight="bold"
|
||||
tintColor="#FFFFFF"
|
||||
/>
|
||||
</Pressable>
|
||||
</Animated.View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<Modal
|
||||
visible={whisperSettingsOpen}
|
||||
animationType="slide"
|
||||
presentationStyle="formSheet"
|
||||
onRequestClose={() => setWhisperSettingsOpen(false)}
|
||||
>
|
||||
<SafeAreaView style={styles.settingsRoot}>
|
||||
<View style={styles.settingsTop}>
|
||||
<View style={styles.settingsTitleBlock}>
|
||||
<Text style={styles.settingsTitle}>Settings</Text>
|
||||
<Text style={styles.settingsSubtitle}>Default: {WHISPER_MODEL_LABELS[defaultWhisperModel]}</Text>
|
||||
</View>
|
||||
<Pressable onPress={() => setWhisperSettingsOpen(false)}>
|
||||
<Text style={styles.settingsClose}>Done</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<ScrollView style={styles.settingsScroll} contentContainerStyle={styles.settingsContent}>
|
||||
<View style={styles.settingsSection}>
|
||||
<Text style={styles.settingsSectionLabel}>DEVELOPMENT:</Text>
|
||||
{__DEV__ ? (
|
||||
<Pressable
|
||||
onPress={handleReplayOnboarding}
|
||||
style={({ pressed }) => [styles.settingsTextRow, pressed && styles.clearButtonPressed]}
|
||||
>
|
||||
<Text style={styles.settingsTextRowTitle}>Replay onboarding</Text>
|
||||
<Text style={styles.settingsTextRowAction}>Run</Text>
|
||||
</Pressable>
|
||||
) : (
|
||||
<View style={styles.settingsTextRow}>
|
||||
<Text style={styles.settingsMutedText}>Available in development builds.</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View style={styles.settingsSection}>
|
||||
<Text style={styles.settingsSectionLabel}>GENERAL:</Text>
|
||||
<View style={styles.settingsTextRow}>
|
||||
<Text style={styles.settingsTextRowTitle}>Default model</Text>
|
||||
<Text style={styles.settingsTextRowValue}>{WHISPER_MODEL_LABELS[defaultWhisperModel]}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.settingsTextRow}>
|
||||
<View style={styles.settingsOptionCopy}>
|
||||
<Text style={styles.settingsTextRowTitle}>Realtime dictation</Text>
|
||||
<Text style={styles.settingsTextRowMeta}>Turn off to transcribe after release</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={transcriptionMode === "realtime"}
|
||||
onValueChange={(enabled) => setTranscriptionMode(enabled ? "realtime" : "bulk")}
|
||||
disabled={dictationSettingsLocked}
|
||||
trackColor={{ false: "#2D2D31", true: "#6A3A33" }}
|
||||
thumbColor={transcriptionMode === "realtime" ? "#FF6B56" : "#F2F2F2"}
|
||||
ios_backgroundColor="#2D2D31"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.settingsTextRow}>
|
||||
<View style={styles.settingsOptionCopy}>
|
||||
<Text style={styles.settingsTextRowTitle}>Auto send on dictation end</Text>
|
||||
<Text style={styles.settingsTextRowMeta}>Send the transcript as soon as recording finishes</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={autoSendOnDictationEnd}
|
||||
onValueChange={setAutoSendOnDictationEnd}
|
||||
disabled={dictationSettingsLocked}
|
||||
trackColor={{ false: "#2D2D31", true: "#6A3A33" }}
|
||||
thumbColor={autoSendOnDictationEnd ? "#FF6B56" : "#F2F2F2"}
|
||||
ios_backgroundColor="#2D2D31"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{WHISPER_MODEL_FAMILIES.map((family) => (
|
||||
<View key={family.key} style={styles.settingsSection}>
|
||||
<Text style={styles.settingsSectionLabel}>{family.label.toUpperCase()}:</Text>
|
||||
<View style={styles.settingsTextRow}>
|
||||
<Text style={styles.settingsMutedText}>{family.description}</Text>
|
||||
</View>
|
||||
{family.models.map((modelID) => {
|
||||
const installed = installedWhisperModels.includes(modelID)
|
||||
const isDefault = defaultWhisperModel === modelID
|
||||
const isDownloading = downloadingModelID === modelID
|
||||
const actionDisabled = (downloadingModelID !== null && !isDownloading) || isTranscribingBulk
|
||||
const downloadPct = Math.round(Math.max(0, Math.min(1, downloadProgress)) * 100)
|
||||
const actionLabel = isDownloading
|
||||
? `${downloadPct}%`
|
||||
: installed
|
||||
? isDefault
|
||||
? "Selected"
|
||||
: "Select"
|
||||
: "Download"
|
||||
const sizeLabel = formatWhisperModelSize(WHISPER_MODEL_SIZES[modelID])
|
||||
const friendlyName = WHISPER_MODEL_LABELS[modelID]
|
||||
const rowMeta = [sizeLabel, installed ? "installed" : null, isDefault ? "default" : null]
|
||||
.filter(Boolean)
|
||||
.join(" · ")
|
||||
|
||||
return (
|
||||
<View key={modelID} style={styles.settingsInlineRow}>
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
if (installed) {
|
||||
void handleSelectWhisperModel(modelID)
|
||||
}
|
||||
}}
|
||||
onLongPress={() => {
|
||||
if (!installed || isDownloading) return
|
||||
Alert.alert("Delete model?", `Remove ${friendlyName} from this device?`, [
|
||||
{ text: "Cancel", style: "cancel" },
|
||||
{
|
||||
text: "Delete",
|
||||
style: "destructive",
|
||||
onPress: () => {
|
||||
void handleDeleteWhisperModel(modelID)
|
||||
},
|
||||
},
|
||||
])
|
||||
}}
|
||||
delayLongPress={350}
|
||||
disabled={!installed || actionDisabled || isPreparingWhisperModel}
|
||||
style={({ pressed }) => [
|
||||
styles.settingsInlineLabelPressable,
|
||||
(!installed || actionDisabled || isPreparingWhisperModel) &&
|
||||
styles.settingsInlinePressableDisabled,
|
||||
pressed && styles.clearButtonPressed,
|
||||
]}
|
||||
>
|
||||
<Text style={styles.settingsInlineName}>{friendlyName}</Text>
|
||||
<Text style={styles.settingsInlineMeta}>{rowMeta}</Text>
|
||||
</Pressable>
|
||||
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
if (isDownloading) return
|
||||
if (installed) {
|
||||
void handleSelectWhisperModel(modelID)
|
||||
return
|
||||
}
|
||||
void handleDownloadWhisperModel(modelID)
|
||||
}}
|
||||
disabled={actionDisabled || (installed && isPreparingWhisperModel)}
|
||||
accessibilityLabel={actionLabel}
|
||||
style={({ pressed }) => [
|
||||
styles.settingsInlineTextActionPressable,
|
||||
(actionDisabled || (installed && isPreparingWhisperModel)) &&
|
||||
styles.settingsInlinePressableDisabled,
|
||||
pressed && styles.clearButtonPressed,
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.settingsInlineTextAction,
|
||||
installed && styles.settingsInlineTextActionInstalled,
|
||||
isDownloading && styles.settingsInlineTextActionDownloading,
|
||||
]}
|
||||
>
|
||||
{actionLabel}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
visible={scanOpen}
|
||||
animationType="slide"
|
||||
presentationStyle="formSheet"
|
||||
onRequestClose={() => setScanOpen(false)}
|
||||
>
|
||||
<SafeAreaView style={styles.scanRoot}>
|
||||
<View style={styles.scanTop}>
|
||||
<Text style={styles.scanTitle}>Scan server QR</Text>
|
||||
<Pressable onPress={() => setScanOpen(false)}>
|
||||
<Text style={styles.scanClose}>Close</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
{camGranted && camera ? (
|
||||
<camera.CameraView
|
||||
style={styles.scanCam}
|
||||
barcodeScannerSettings={{ barcodeTypes: ["qr"] }}
|
||||
onBarcodeScanned={handleScan}
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.scanEmpty}>
|
||||
<Text style={styles.scanHint}>Camera permission is required to scan setup QR codes.</Text>
|
||||
</View>
|
||||
)}
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
visible={pairSelectionOpen}
|
||||
animationType="slide"
|
||||
presentationStyle="formSheet"
|
||||
onRequestClose={closePairSelection}
|
||||
>
|
||||
<SafeAreaView style={styles.pairSelectRoot}>
|
||||
<View style={styles.pairSelectTop}>
|
||||
<View style={styles.pairSelectTitleBlock}>
|
||||
<Text style={styles.pairSelectTitle}>Choose server host</Text>
|
||||
<Text style={styles.pairSelectSubtitle}>Select the best network route for this server.</Text>
|
||||
</View>
|
||||
<Pressable onPress={closePairSelection}>
|
||||
<Text style={styles.pairSelectClose}>Close</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
<ScrollView style={styles.pairSelectList} contentContainerStyle={styles.pairSelectListContent}>
|
||||
{pairHostOptions.map((option, index) => {
|
||||
const probe = pairHostProbes[option.url]
|
||||
const selected = selectedPairHostURL === option.url
|
||||
const recommended = recommendedPairHostURL === option.url
|
||||
let dotStyle = styles.pairSelectDotChecking
|
||||
if (probe?.status === "online") {
|
||||
dotStyle = styles.pairSelectDotOnline
|
||||
} else if (probe?.status === "offline") {
|
||||
dotStyle = styles.pairSelectDotOffline
|
||||
}
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
key={option.url}
|
||||
onPress={() => setSelectedPairHostURL(option.url)}
|
||||
style={({ pressed }) => [
|
||||
styles.pairSelectRow,
|
||||
selected && styles.pairSelectRowSelected,
|
||||
index === pairHostOptions.length - 1 && styles.pairSelectRowLast,
|
||||
pressed && styles.clearButtonPressed,
|
||||
]}
|
||||
>
|
||||
<View style={styles.pairSelectRowMain}>
|
||||
<View style={styles.pairSelectLeftCol}>
|
||||
<View style={[styles.pairSelectDot, dotStyle]} />
|
||||
<View style={styles.pairSelectRowCopy}>
|
||||
<View style={styles.pairSelectRowTitleLine}>
|
||||
<Text style={styles.pairSelectHostLabel} numberOfLines={1}>
|
||||
{option.label}
|
||||
</Text>
|
||||
{recommended ? <Text style={styles.pairSelectRecommended}>recommended</Text> : null}
|
||||
</View>
|
||||
<Text style={styles.pairSelectHostMeta}>{pairHostKindLabel(option.kind)}</Text>
|
||||
<Text style={styles.pairSelectProbeMeta}>{pairProbeSummary(probe)}</Text>
|
||||
<Text style={styles.pairSelectHostURL} numberOfLines={1} ellipsizeMode="middle">
|
||||
{option.url}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.pairSelectRightCol}>
|
||||
<Text style={styles.pairSelectLatency}>{pairProbeLabel(probe)}</Text>
|
||||
{selected ? (
|
||||
<SymbolView
|
||||
name={{
|
||||
ios: "checkmark",
|
||||
android: "check",
|
||||
web: "check",
|
||||
}}
|
||||
size={13}
|
||||
tintColor="#C5C5C5"
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
</Pressable>
|
||||
)
|
||||
})}
|
||||
</ScrollView>
|
||||
|
||||
<View style={styles.pairSelectFooter}>
|
||||
<Pressable
|
||||
onPress={handleConnectSelectedPairHost}
|
||||
disabled={!selectedPairHostURL || isConnectingPairHost}
|
||||
style={({ pressed }) => [
|
||||
styles.pairSelectPrimaryButton,
|
||||
(!selectedPairHostURL || isConnectingPairHost) && styles.pairSelectPrimaryButtonDisabled,
|
||||
pressed && styles.clearButtonPressed,
|
||||
]}
|
||||
>
|
||||
<Text style={styles.pairSelectPrimaryButtonText}>
|
||||
{isConnectingPairHost ? "Connecting..." : "Connect selected host"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
|
||||
<Pressable
|
||||
onPress={handleRescanFromPairSelection}
|
||||
style={({ pressed }) => [pressed && styles.clearButtonPressed]}
|
||||
>
|
||||
<Text style={styles.pairSelectSecondaryAction}>Scan again</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</Modal>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: C.bg,
|
||||
position: "relative",
|
||||
},
|
||||
onboardingRoot: {
|
||||
flex: 1,
|
||||
backgroundColor: C.bg,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
onboardingShell: {
|
||||
flex: 1,
|
||||
},
|
||||
onboardingTopRail: {
|
||||
gap: 8,
|
||||
marginBottom: 10,
|
||||
},
|
||||
onboardingContent: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "stretch",
|
||||
gap: 22,
|
||||
paddingHorizontal: 2,
|
||||
},
|
||||
onboardingModelRow: {
|
||||
gap: 6,
|
||||
},
|
||||
onboardingModelText: {
|
||||
color: "#A9A9A9",
|
||||
fontSize: 11,
|
||||
fontWeight: "700",
|
||||
letterSpacing: 0.35,
|
||||
textTransform: "uppercase",
|
||||
},
|
||||
onboardingModelTrack: {
|
||||
height: 4,
|
||||
width: "100%",
|
||||
borderRadius: 999,
|
||||
backgroundColor: "#2C2C2C",
|
||||
overflow: "hidden",
|
||||
},
|
||||
onboardingModelFill: {
|
||||
height: "100%",
|
||||
borderRadius: 999,
|
||||
backgroundColor: "#FF5B47",
|
||||
},
|
||||
onboardingVisualSurface: {
|
||||
width: "100%",
|
||||
minHeight: 176,
|
||||
borderRadius: 26,
|
||||
borderWidth: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
overflow: "hidden",
|
||||
backgroundColor: "#171717",
|
||||
borderColor: "#2B2B2B",
|
||||
},
|
||||
onboardingVisualSurfaceMic: {
|
||||
backgroundColor: "#1A2118",
|
||||
borderColor: "#2F3D2D",
|
||||
},
|
||||
onboardingVisualSurfaceNotifications: {
|
||||
backgroundColor: "#1A1D2A",
|
||||
borderColor: "#303A5A",
|
||||
},
|
||||
onboardingVisualSurfaceNetwork: {
|
||||
backgroundColor: "#1A2218",
|
||||
borderColor: "#344930",
|
||||
},
|
||||
onboardingVisualSurfacePair: {
|
||||
backgroundColor: "#1F1A27",
|
||||
borderColor: "#413157",
|
||||
},
|
||||
onboardingVisualOrb: {
|
||||
position: "absolute",
|
||||
borderRadius: 999,
|
||||
opacity: 0.22,
|
||||
},
|
||||
onboardingVisualOrbOne: {
|
||||
width: 130,
|
||||
height: 130,
|
||||
top: -28,
|
||||
left: -22,
|
||||
},
|
||||
onboardingVisualOrbTwo: {
|
||||
width: 160,
|
||||
height: 160,
|
||||
bottom: -52,
|
||||
right: -44,
|
||||
},
|
||||
onboardingVisualOrbMic: {
|
||||
backgroundColor: "#61C372",
|
||||
},
|
||||
onboardingVisualOrbNotifications: {
|
||||
backgroundColor: "#4A6EE0",
|
||||
},
|
||||
onboardingVisualOrbNetwork: {
|
||||
backgroundColor: "#78B862",
|
||||
},
|
||||
onboardingVisualOrbPair: {
|
||||
backgroundColor: "#9B6CDC",
|
||||
},
|
||||
onboardingVisualTag: {
|
||||
borderRadius: 20,
|
||||
paddingHorizontal: 24,
|
||||
paddingVertical: 12,
|
||||
borderWidth: 1,
|
||||
shadowColor: "#000000",
|
||||
shadowOffset: { width: 0, height: 8 },
|
||||
shadowOpacity: 0.12,
|
||||
shadowRadius: 16,
|
||||
elevation: 3,
|
||||
},
|
||||
onboardingVisualTagMic: {
|
||||
backgroundColor: "#253A25",
|
||||
borderColor: "#3A5C3A",
|
||||
},
|
||||
onboardingVisualTagNotifications: {
|
||||
backgroundColor: "#223561",
|
||||
borderColor: "#38518C",
|
||||
},
|
||||
onboardingVisualTagNetwork: {
|
||||
backgroundColor: "#284122",
|
||||
borderColor: "#3D6835",
|
||||
},
|
||||
onboardingVisualTagPair: {
|
||||
backgroundColor: "#3B2859",
|
||||
borderColor: "#5A3D86",
|
||||
},
|
||||
onboardingVisualTagText: {
|
||||
color: "#F6F7F8",
|
||||
fontSize: 22,
|
||||
fontWeight: "800",
|
||||
letterSpacing: 1.8,
|
||||
},
|
||||
onboardingCopyBlock: {
|
||||
alignItems: "flex-start",
|
||||
gap: 10,
|
||||
width: "100%",
|
||||
},
|
||||
onboardingEyebrow: {
|
||||
color: "#7F7F7F",
|
||||
fontSize: 11,
|
||||
fontWeight: "700",
|
||||
letterSpacing: 1.3,
|
||||
},
|
||||
onboardingTitle: {
|
||||
color: C.textPrimary,
|
||||
fontSize: 34,
|
||||
fontWeight: "800",
|
||||
textAlign: "left",
|
||||
letterSpacing: -1,
|
||||
lineHeight: 38,
|
||||
},
|
||||
onboardingBody: {
|
||||
color: "#B4B4B4",
|
||||
fontSize: 18,
|
||||
lineHeight: 25,
|
||||
textAlign: "left",
|
||||
paddingHorizontal: 0,
|
||||
},
|
||||
onboardingFooter: {
|
||||
gap: 10,
|
||||
paddingTop: 6,
|
||||
},
|
||||
onboardingPrimaryButton: {
|
||||
minHeight: BTN_PRIMARY.minHeight,
|
||||
borderRadius: BTN_PRIMARY.borderRadius,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: BTN_PRIMARY.backgroundColor,
|
||||
borderWidth: BTN_PRIMARY.borderWidth,
|
||||
borderColor: BTN_PRIMARY.borderColor,
|
||||
flexDirection: "row",
|
||||
gap: 10,
|
||||
shadowColor: "#000000",
|
||||
shadowOffset: { width: 0, height: 8 },
|
||||
shadowOpacity: 0.2,
|
||||
shadowRadius: 18,
|
||||
elevation: 4,
|
||||
},
|
||||
onboardingPrimaryButtonDisabled: {
|
||||
opacity: 0.6,
|
||||
},
|
||||
onboardingPrimaryButtonText: {
|
||||
color: "#FFFFFF",
|
||||
fontSize: 17,
|
||||
fontWeight: "700",
|
||||
letterSpacing: 0.2,
|
||||
},
|
||||
onboardingSecondaryButton: {
|
||||
alignSelf: "flex-start",
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 2,
|
||||
},
|
||||
onboardingSecondaryText: {
|
||||
color: "#959CAA",
|
||||
fontSize: 14,
|
||||
fontWeight: "600",
|
||||
textAlign: "left",
|
||||
},
|
||||
dismissOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
zIndex: 15,
|
||||
},
|
||||
headerAnchor: {
|
||||
marginHorizontal: 6,
|
||||
marginTop: 5,
|
||||
height: 51,
|
||||
zIndex: 30,
|
||||
},
|
||||
statusBar: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundColor: C.surface,
|
||||
borderRadius: 20,
|
||||
borderWidth: 3,
|
||||
borderColor: C.border,
|
||||
paddingHorizontal: 14,
|
||||
paddingTop: 0,
|
||||
overflow: "hidden",
|
||||
shadowColor: "#000000",
|
||||
},
|
||||
statusBarInner: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
minHeight: 30,
|
||||
},
|
||||
statusBarTapArea: {
|
||||
height: 45,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
},
|
||||
headerServerLabel: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
width: "100%",
|
||||
},
|
||||
headerSplitRow: {
|
||||
height: 45,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
headerSplitLeft: {
|
||||
flex: 1,
|
||||
flexBasis: 0,
|
||||
minWidth: 0,
|
||||
height: "100%",
|
||||
justifyContent: "center",
|
||||
alignItems: "flex-start",
|
||||
paddingRight: 8,
|
||||
},
|
||||
headerSplitDivider: {
|
||||
width: 4,
|
||||
height: 4,
|
||||
borderRadius: 2,
|
||||
backgroundColor: "#3F4556",
|
||||
marginHorizontal: 6,
|
||||
},
|
||||
headerSplitRight: {
|
||||
flex: 1,
|
||||
flexBasis: 0,
|
||||
minWidth: 0,
|
||||
height: "100%",
|
||||
justifyContent: "center",
|
||||
alignItems: "flex-start",
|
||||
paddingLeft: 8,
|
||||
},
|
||||
workspaceHeaderText: {
|
||||
color: "#8F8F8F",
|
||||
fontSize: 14,
|
||||
fontWeight: "600",
|
||||
},
|
||||
headerServerText: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
width: "100%",
|
||||
},
|
||||
headerSessionText: {
|
||||
flexShrink: 1,
|
||||
minWidth: 0,
|
||||
width: "100%",
|
||||
textAlign: "left",
|
||||
},
|
||||
serverMenuInline: {
|
||||
marginTop: 8,
|
||||
paddingBottom: 2,
|
||||
gap: 4,
|
||||
},
|
||||
dropdownListViewport: {
|
||||
maxHeight: DROPDOWN_VISIBLE_ROWS * DROPDOWN_ROW_HEIGHT,
|
||||
},
|
||||
dropdownListContent: {
|
||||
paddingBottom: 0,
|
||||
},
|
||||
currentSessionSummary: {
|
||||
paddingHorizontal: 4,
|
||||
paddingTop: 2,
|
||||
paddingBottom: 8,
|
||||
gap: 5,
|
||||
},
|
||||
currentSessionLabel: {
|
||||
color: "#A3ACC0",
|
||||
fontSize: 12,
|
||||
fontWeight: "700",
|
||||
letterSpacing: 0.4,
|
||||
textTransform: "uppercase",
|
||||
},
|
||||
currentSessionMetaRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
},
|
||||
currentSessionMetaKey: {
|
||||
width: 74,
|
||||
color: "#7C8599",
|
||||
fontSize: 12,
|
||||
fontWeight: "600",
|
||||
},
|
||||
currentSessionMetaValue: {
|
||||
flex: 1,
|
||||
color: "#D7DCE6",
|
||||
fontSize: 13,
|
||||
fontWeight: "500",
|
||||
},
|
||||
currentSessionDivider: {
|
||||
width: "100%",
|
||||
height: 1,
|
||||
backgroundColor: "#222733",
|
||||
marginBottom: 4,
|
||||
},
|
||||
serverGroupHeaderRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginTop: 8,
|
||||
},
|
||||
serverGroupLabel: {
|
||||
color: "#8F97AA",
|
||||
fontSize: 12,
|
||||
fontWeight: "700",
|
||||
letterSpacing: 0.4,
|
||||
textTransform: "uppercase",
|
||||
paddingHorizontal: 4,
|
||||
paddingVertical: 4,
|
||||
},
|
||||
serverEmptyText: {
|
||||
color: C.textDimmed,
|
||||
fontSize: 14,
|
||||
textAlign: "center",
|
||||
paddingVertical: 10,
|
||||
},
|
||||
serverGroupEmptyText: {
|
||||
textAlign: "left",
|
||||
paddingHorizontal: 4,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
serverRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
paddingHorizontal: 4,
|
||||
paddingVertical: 8,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#222733",
|
||||
},
|
||||
serverRowLast: {
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
serverRowPressed: {
|
||||
opacity: 0.85,
|
||||
},
|
||||
serverStatusDot: {
|
||||
width: 9,
|
||||
height: 9,
|
||||
borderRadius: 5,
|
||||
},
|
||||
serverStatusActive: {
|
||||
backgroundColor: C.green,
|
||||
},
|
||||
serverStatusChecking: {
|
||||
backgroundColor: C.yellow,
|
||||
},
|
||||
serverStatusOffline: {
|
||||
backgroundColor: "#D14C55",
|
||||
},
|
||||
serverNameText: {
|
||||
flex: 1,
|
||||
color: C.textSecondary,
|
||||
fontSize: 16,
|
||||
fontWeight: "500",
|
||||
},
|
||||
sessionUpdatedText: {
|
||||
color: "#8E96A8",
|
||||
fontSize: 14,
|
||||
fontWeight: "500",
|
||||
marginLeft: 8,
|
||||
},
|
||||
discoveredServerCopy: {
|
||||
flex: 1,
|
||||
gap: 2,
|
||||
},
|
||||
discoveredServerMeta: {
|
||||
color: "#818A9E",
|
||||
fontSize: 12,
|
||||
fontWeight: "500",
|
||||
},
|
||||
discoveredServerAction: {
|
||||
color: "#B9C2D8",
|
||||
fontSize: 13,
|
||||
fontWeight: "700",
|
||||
},
|
||||
discoveryErrorText: {
|
||||
color: "#7D8598",
|
||||
fontSize: 11,
|
||||
fontWeight: "500",
|
||||
paddingHorizontal: 4,
|
||||
paddingTop: 4,
|
||||
},
|
||||
// serverDeleteIcon: removed — replaced with SymbolView
|
||||
addServerButton: {
|
||||
marginTop: 4,
|
||||
alignSelf: "center",
|
||||
paddingHorizontal: 8,
|
||||
paddingTop: 2,
|
||||
paddingBottom: 10,
|
||||
},
|
||||
addServerButtonText: {
|
||||
color: "#B8BDC9",
|
||||
fontSize: 16,
|
||||
fontWeight: "600",
|
||||
},
|
||||
sessionMenuActions: {
|
||||
marginTop: 2,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: "#222733",
|
||||
},
|
||||
sessionMenuActionRow: {
|
||||
paddingVertical: 9,
|
||||
},
|
||||
sessionMenuActionInner: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
},
|
||||
sessionMenuActionIconSlot: {
|
||||
width: 9,
|
||||
height: 9,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
sessionMenuActionButtonDisabled: {
|
||||
opacity: 0.55,
|
||||
},
|
||||
sessionMenuActionText: {
|
||||
flex: 1,
|
||||
color: "#D6DAE4",
|
||||
fontSize: 16,
|
||||
fontWeight: "500",
|
||||
},
|
||||
statusLeft: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
},
|
||||
readyDot: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
backgroundColor: AGENT_SUCCESS_GREEN,
|
||||
},
|
||||
recordingDot: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
backgroundColor: "#FF2E3F",
|
||||
},
|
||||
statusText: {
|
||||
fontSize: 14,
|
||||
fontWeight: "500",
|
||||
color: "#666",
|
||||
},
|
||||
statusActions: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
},
|
||||
clearButton: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
alignSelf: "center",
|
||||
},
|
||||
clearButtonPressed: {
|
||||
opacity: 0.75,
|
||||
},
|
||||
// clearIcon: removed — replaced with SymbolView
|
||||
transcriptionArea: {
|
||||
flex: 1,
|
||||
marginHorizontal: 6,
|
||||
marginTop: 6,
|
||||
},
|
||||
splitCardStack: {
|
||||
flex: 1,
|
||||
gap: 8,
|
||||
},
|
||||
splitCard: {
|
||||
flex: 1,
|
||||
backgroundColor: C.surface,
|
||||
borderRadius: 20,
|
||||
borderWidth: 3,
|
||||
borderColor: C.border,
|
||||
overflow: "hidden",
|
||||
position: "relative",
|
||||
},
|
||||
replyCard: {
|
||||
paddingTop: 16,
|
||||
},
|
||||
permissionCard: {
|
||||
paddingTop: 16,
|
||||
},
|
||||
permissionHeaderRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 10,
|
||||
marginHorizontal: 20,
|
||||
marginBottom: 12,
|
||||
},
|
||||
permissionHeaderCopy: {
|
||||
flex: 1,
|
||||
gap: 2,
|
||||
},
|
||||
permissionStatusDot: {
|
||||
width: 10,
|
||||
height: 10,
|
||||
borderRadius: 999,
|
||||
backgroundColor: C.orange,
|
||||
},
|
||||
permissionEyebrow: {
|
||||
color: C.orange,
|
||||
fontSize: 11,
|
||||
fontWeight: "800",
|
||||
letterSpacing: 1.1,
|
||||
},
|
||||
permissionStatusText: {
|
||||
color: "#9099AA",
|
||||
fontSize: 13,
|
||||
fontWeight: "600",
|
||||
},
|
||||
permissionScroll: {
|
||||
flex: 1,
|
||||
},
|
||||
permissionContent: {
|
||||
paddingHorizontal: 20,
|
||||
paddingBottom: 20,
|
||||
gap: 14,
|
||||
},
|
||||
permissionTitle: {
|
||||
color: "#F7F8FB",
|
||||
fontSize: 30,
|
||||
fontWeight: "800",
|
||||
lineHeight: 36,
|
||||
letterSpacing: -0.7,
|
||||
},
|
||||
permissionBody: {
|
||||
color: "#B2BDCF",
|
||||
fontSize: 17,
|
||||
fontWeight: "500",
|
||||
lineHeight: 24,
|
||||
},
|
||||
permissionSection: {
|
||||
gap: 6,
|
||||
paddingVertical: 14,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#242424",
|
||||
},
|
||||
permissionSectionLast: {
|
||||
borderBottomWidth: 0,
|
||||
},
|
||||
permissionSectionLabel: {
|
||||
color: "#7F8798",
|
||||
fontSize: 11,
|
||||
fontWeight: "700",
|
||||
letterSpacing: 0.9,
|
||||
textTransform: "uppercase",
|
||||
},
|
||||
permissionSectionText: {
|
||||
color: "#E7E7E7",
|
||||
fontSize: 14,
|
||||
fontWeight: "500",
|
||||
lineHeight: 20,
|
||||
},
|
||||
permissionSectionTextMono: {
|
||||
fontFamily: Platform.select({ ios: "Menlo", android: "monospace", web: "monospace" }),
|
||||
fontSize: 12,
|
||||
lineHeight: 18,
|
||||
color: "#D4D7DE",
|
||||
backgroundColor: "#17181B",
|
||||
borderRadius: 8,
|
||||
overflow: "hidden",
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 4,
|
||||
},
|
||||
permissionFooter: {
|
||||
gap: 10,
|
||||
paddingHorizontal: 20,
|
||||
paddingBottom: 18,
|
||||
paddingTop: 8,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: "#21252F",
|
||||
},
|
||||
permissionPrimaryButton: {
|
||||
minHeight: BTN_PRIMARY.minHeight,
|
||||
borderRadius: BTN_PRIMARY.borderRadius,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: BTN_PRIMARY.backgroundColor,
|
||||
borderWidth: BTN_PRIMARY.borderWidth,
|
||||
borderColor: BTN_PRIMARY.borderColor,
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
permissionPrimaryButtonText: {
|
||||
color: "#FFFFFF",
|
||||
fontSize: 16,
|
||||
fontWeight: "800",
|
||||
letterSpacing: 0.2,
|
||||
},
|
||||
permissionSecondaryRow: {
|
||||
flexDirection: "row",
|
||||
gap: 10,
|
||||
},
|
||||
permissionSecondaryButton: {
|
||||
flex: 1,
|
||||
minHeight: 48,
|
||||
borderRadius: 14,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "#1C1E22",
|
||||
borderWidth: 1,
|
||||
borderColor: "#32353D",
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
permissionSecondaryButtonText: {
|
||||
color: "#E0E3EA",
|
||||
fontSize: 14,
|
||||
fontWeight: "700",
|
||||
},
|
||||
permissionRejectButton: {
|
||||
flex: 1,
|
||||
minHeight: 48,
|
||||
borderRadius: 14,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "#31181C",
|
||||
borderWidth: 1,
|
||||
borderColor: "#5E2B34",
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
permissionRejectButtonWide: {
|
||||
flex: 1,
|
||||
},
|
||||
permissionRejectButtonText: {
|
||||
color: "#FFCCD2",
|
||||
fontSize: 14,
|
||||
fontWeight: "700",
|
||||
},
|
||||
permissionActionDisabled: {
|
||||
opacity: 0.6,
|
||||
},
|
||||
transcriptionPanel: {
|
||||
flex: 1,
|
||||
position: "relative",
|
||||
overflow: "hidden",
|
||||
},
|
||||
replyCardLabel: {
|
||||
color: "#AAB5CC",
|
||||
fontSize: 15,
|
||||
fontWeight: "600",
|
||||
},
|
||||
readerCard: {
|
||||
paddingTop: 16,
|
||||
},
|
||||
readerOverlayCard: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 2,
|
||||
},
|
||||
readerHeaderRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginHorizontal: 20,
|
||||
marginBottom: 8,
|
||||
},
|
||||
readerScroll: {
|
||||
flex: 1,
|
||||
},
|
||||
readerContent: {
|
||||
paddingHorizontal: 20,
|
||||
paddingBottom: 18,
|
||||
gap: 14,
|
||||
},
|
||||
readerParagraph: {
|
||||
color: "#E8EDF8",
|
||||
fontSize: 22,
|
||||
fontWeight: "500",
|
||||
lineHeight: 32,
|
||||
},
|
||||
readerHeading: {
|
||||
color: "#F7F9FF",
|
||||
fontWeight: "800",
|
||||
letterSpacing: -0.2,
|
||||
},
|
||||
readerHeading1: {
|
||||
fontSize: 29,
|
||||
lineHeight: 38,
|
||||
},
|
||||
readerHeading2: {
|
||||
fontSize: 25,
|
||||
lineHeight: 34,
|
||||
},
|
||||
readerHeading3: {
|
||||
fontSize: 22,
|
||||
lineHeight: 30,
|
||||
},
|
||||
markdownItalic: {
|
||||
fontStyle: "italic",
|
||||
},
|
||||
markdownBold: {
|
||||
fontWeight: "800",
|
||||
},
|
||||
markdownBoldItalic: {
|
||||
fontStyle: "italic",
|
||||
fontWeight: "800",
|
||||
},
|
||||
readerInlineCode: {
|
||||
color: "#E7EBF5",
|
||||
backgroundColor: "#17181B",
|
||||
borderWidth: 1,
|
||||
borderColor: "#292A2E",
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 6,
|
||||
paddingVertical: 2,
|
||||
fontSize: 18,
|
||||
lineHeight: 26,
|
||||
fontFamily: Platform.select({ ios: "Menlo", android: "monospace", web: "monospace" }),
|
||||
},
|
||||
readerCodeBlock: {
|
||||
borderRadius: 14,
|
||||
borderWidth: 1,
|
||||
borderColor: "#292A2E",
|
||||
backgroundColor: "#17181B",
|
||||
paddingHorizontal: 14,
|
||||
paddingVertical: 12,
|
||||
gap: 8,
|
||||
},
|
||||
readerCodeLanguage: {
|
||||
color: "#97A5C2",
|
||||
fontSize: 11,
|
||||
fontWeight: "700",
|
||||
letterSpacing: 0.8,
|
||||
textTransform: "uppercase",
|
||||
},
|
||||
readerCodeText: {
|
||||
color: "#DDE6F7",
|
||||
fontSize: 18,
|
||||
lineHeight: 28,
|
||||
fontFamily: Platform.select({ ios: "Menlo", android: "monospace", web: "monospace" }),
|
||||
},
|
||||
agentStateHeaderRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginHorizontal: 20,
|
||||
marginBottom: 8,
|
||||
},
|
||||
agentStateTitleWrap: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
},
|
||||
agentStateIconWrap: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
headerStatusIconWrap: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
justifyContent: "center",
|
||||
paddingLeft: 5,
|
||||
},
|
||||
agentStateActions: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
width: READER_ACTION_RAIL_WIDTH,
|
||||
gap: READER_ACTION_GAP,
|
||||
},
|
||||
agentStateActionsSingle: {
|
||||
width: READER_ACTION_SIZE,
|
||||
gap: 0,
|
||||
},
|
||||
agentStateActionButton: {
|
||||
width: READER_ACTION_SIZE,
|
||||
height: READER_ACTION_SIZE,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
agentStateReader: {
|
||||
color: "#8FA4CC",
|
||||
fontSize: 13,
|
||||
fontWeight: "700",
|
||||
letterSpacing: 0.2,
|
||||
},
|
||||
readerActionRail: {
|
||||
width: READER_ACTION_RAIL_WIDTH,
|
||||
height: READER_ACTION_SIZE,
|
||||
position: "relative",
|
||||
alignItems: "flex-end",
|
||||
justifyContent: "center",
|
||||
},
|
||||
readerToggleFloatingAction: {
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
width: READER_ACTION_SIZE,
|
||||
height: READER_ACTION_SIZE,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
readerToggleIconLayer: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
readerToggleSymbol: {
|
||||
transform: [{ rotate: "-90deg" }],
|
||||
},
|
||||
// agentStateClose: removed — replaced with SymbolView
|
||||
replyScroll: {
|
||||
flex: 1,
|
||||
},
|
||||
replyContent: {
|
||||
paddingHorizontal: 20,
|
||||
paddingBottom: 18,
|
||||
flexGrow: 1,
|
||||
gap: 14,
|
||||
},
|
||||
replyText: {
|
||||
fontSize: 22,
|
||||
fontWeight: "500",
|
||||
lineHeight: 32,
|
||||
color: "#F4F7FF",
|
||||
},
|
||||
replyHeading: {
|
||||
color: "#F7F9FF",
|
||||
fontWeight: "800",
|
||||
letterSpacing: -0.2,
|
||||
},
|
||||
replyHeading1: {
|
||||
fontSize: 27,
|
||||
lineHeight: 36,
|
||||
},
|
||||
replyHeading2: {
|
||||
fontSize: 24,
|
||||
lineHeight: 32,
|
||||
},
|
||||
replyHeading3: {
|
||||
fontSize: 21,
|
||||
lineHeight: 29,
|
||||
},
|
||||
transcriptionScroll: {
|
||||
flex: 1,
|
||||
},
|
||||
transcriptionContent: {
|
||||
padding: 20,
|
||||
paddingTop: 54,
|
||||
paddingBottom: 54,
|
||||
flexGrow: 1,
|
||||
},
|
||||
transcriptionTopActions: {
|
||||
position: "absolute",
|
||||
top: 10,
|
||||
left: 10,
|
||||
right: 10,
|
||||
zIndex: 4,
|
||||
flexDirection: "row",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
promptHistoryLabel: {
|
||||
color: "#555",
|
||||
fontSize: 13,
|
||||
fontWeight: "700",
|
||||
letterSpacing: 0.6,
|
||||
textTransform: "uppercase",
|
||||
marginBottom: 8,
|
||||
},
|
||||
promptHistoryText: {
|
||||
fontSize: 24,
|
||||
fontWeight: "500",
|
||||
lineHeight: 34,
|
||||
color: "#888",
|
||||
},
|
||||
modelErrorBadge: {
|
||||
alignSelf: "flex-start",
|
||||
marginLeft: 14,
|
||||
marginTop: 8,
|
||||
marginBottom: 2,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 5,
|
||||
borderRadius: 999,
|
||||
backgroundColor: "#3A1A1D",
|
||||
borderWidth: 1,
|
||||
borderColor: "#5D292F",
|
||||
},
|
||||
modelErrorText: {
|
||||
color: "#FFB9BF",
|
||||
fontSize: 12,
|
||||
fontWeight: "600",
|
||||
letterSpacing: 0.1,
|
||||
},
|
||||
transcriptionText: {
|
||||
fontSize: 28,
|
||||
fontWeight: "500",
|
||||
lineHeight: 38,
|
||||
color: "#FFFFFF",
|
||||
},
|
||||
placeholderText: {
|
||||
fontSize: 28,
|
||||
fontWeight: "500",
|
||||
color: C.textPlaceholder,
|
||||
},
|
||||
transcriptionContentLive: {
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
swipeHint: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
alignSelf: "flex-end",
|
||||
gap: 6,
|
||||
},
|
||||
swipeHintText: {
|
||||
color: "#444",
|
||||
fontSize: 13,
|
||||
fontWeight: "500",
|
||||
},
|
||||
swipeHintArrow: {
|
||||
color: "#444",
|
||||
fontSize: 15,
|
||||
fontWeight: "600",
|
||||
},
|
||||
waveformBoxesRow: {
|
||||
position: "absolute",
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 14,
|
||||
height: WAVEFORM_ROWS * WAVEFORM_CELL_SIZE + (WAVEFORM_ROWS - 1) * WAVEFORM_CELL_GAP,
|
||||
pointerEvents: "none",
|
||||
},
|
||||
waveformGridRow: {
|
||||
flexDirection: "row",
|
||||
gap: WAVEFORM_CELL_GAP,
|
||||
marginBottom: WAVEFORM_CELL_GAP,
|
||||
},
|
||||
waveformBox: {
|
||||
width: WAVEFORM_CELL_SIZE,
|
||||
height: WAVEFORM_CELL_SIZE,
|
||||
borderRadius: 1,
|
||||
backgroundColor: "#78839A",
|
||||
borderWidth: 1,
|
||||
borderColor: "#818DA6",
|
||||
},
|
||||
controlsRow: {
|
||||
paddingHorizontal: 6,
|
||||
paddingBottom: 6,
|
||||
paddingTop: 6,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
recordPressable: {
|
||||
flex: 1,
|
||||
},
|
||||
recordButton: {
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: C.redBg,
|
||||
height: CONTROL_HEIGHT,
|
||||
borderRadius: 20,
|
||||
width: "100%",
|
||||
overflow: "hidden",
|
||||
},
|
||||
recordBusyCenter: {
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
},
|
||||
loadFill: {
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: "#FF5B47",
|
||||
},
|
||||
loadFillPending: {
|
||||
backgroundColor: "#66423C",
|
||||
},
|
||||
loadOverlay: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
paddingHorizontal: 18,
|
||||
},
|
||||
loadText: {
|
||||
color: "#FFF6F4",
|
||||
fontSize: 14,
|
||||
fontWeight: "700",
|
||||
letterSpacing: 0.2,
|
||||
},
|
||||
settingsRoot: {
|
||||
flex: 1,
|
||||
backgroundColor: C.bg,
|
||||
paddingHorizontal: 16,
|
||||
paddingTop: 12,
|
||||
},
|
||||
settingsTop: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: 12,
|
||||
marginBottom: 4,
|
||||
},
|
||||
settingsTitleBlock: {
|
||||
flex: 1,
|
||||
gap: 4,
|
||||
},
|
||||
settingsTitle: {
|
||||
color: C.textPrimary,
|
||||
fontSize: 20,
|
||||
fontWeight: "700",
|
||||
},
|
||||
settingsSubtitle: {
|
||||
color: "#999999",
|
||||
fontSize: 13,
|
||||
fontWeight: "500",
|
||||
},
|
||||
settingsClose: {
|
||||
color: "#C5C5C5",
|
||||
fontSize: 15,
|
||||
fontWeight: "700",
|
||||
},
|
||||
settingsScroll: {
|
||||
flex: 1,
|
||||
},
|
||||
settingsContent: {
|
||||
gap: 24,
|
||||
paddingBottom: 24,
|
||||
},
|
||||
settingsSection: {
|
||||
gap: 0,
|
||||
},
|
||||
settingsSectionLabel: {
|
||||
color: "#7D7D7D",
|
||||
fontSize: 11,
|
||||
fontWeight: "700",
|
||||
letterSpacing: 1.05,
|
||||
marginBottom: 6,
|
||||
},
|
||||
settingsTextRow: {
|
||||
minHeight: 46,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: C.borderMuted,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
settingsToggleRow: {
|
||||
alignItems: "flex-start",
|
||||
},
|
||||
settingsMutedText: {
|
||||
color: "#868686",
|
||||
fontSize: 12,
|
||||
fontWeight: "500",
|
||||
},
|
||||
settingsOptionCopy: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
gap: 2,
|
||||
},
|
||||
settingsTextRowTitle: {
|
||||
color: "#ECECEC",
|
||||
fontSize: 14,
|
||||
fontWeight: "600",
|
||||
},
|
||||
settingsTextRowMeta: {
|
||||
color: "#8D8D8D",
|
||||
fontSize: 12,
|
||||
fontWeight: "500",
|
||||
},
|
||||
settingsTextRowValue: {
|
||||
color: "#BDBDBD",
|
||||
fontSize: 13,
|
||||
fontWeight: "600",
|
||||
maxWidth: "55%",
|
||||
textAlign: "right",
|
||||
},
|
||||
settingsTextRowAction: {
|
||||
color: "#B8B8B8",
|
||||
fontSize: 12,
|
||||
fontWeight: "700",
|
||||
letterSpacing: 0.2,
|
||||
},
|
||||
settingsTextRowActionActive: {
|
||||
color: "#FFD8D2",
|
||||
},
|
||||
settingsModeToggle: {
|
||||
flexDirection: "row",
|
||||
backgroundColor: "#17181B",
|
||||
borderWidth: 1,
|
||||
borderColor: "#292A2E",
|
||||
borderRadius: 14,
|
||||
padding: 4,
|
||||
gap: 4,
|
||||
alignSelf: "stretch",
|
||||
},
|
||||
settingsModeToggleOption: {
|
||||
flex: 1,
|
||||
minHeight: 40,
|
||||
borderRadius: 10,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
paddingHorizontal: 12,
|
||||
},
|
||||
settingsModeToggleOptionActive: {
|
||||
backgroundColor: "#3F201B",
|
||||
},
|
||||
settingsModeToggleOptionPressed: {
|
||||
opacity: 0.82,
|
||||
},
|
||||
settingsModeToggleText: {
|
||||
color: "#9A9A9A",
|
||||
fontSize: 13,
|
||||
fontWeight: "700",
|
||||
},
|
||||
settingsModeToggleTextActive: {
|
||||
color: "#FFF0EC",
|
||||
},
|
||||
settingsInlineRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
minHeight: 52,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: C.borderMuted,
|
||||
},
|
||||
settingsInlineLabelPressable: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
paddingVertical: 10,
|
||||
paddingRight: 12,
|
||||
gap: 2,
|
||||
},
|
||||
settingsInlinePressableDisabled: {
|
||||
opacity: 0.55,
|
||||
},
|
||||
settingsInlineName: {
|
||||
color: "#E7E7E7",
|
||||
fontSize: 14,
|
||||
fontWeight: "600",
|
||||
},
|
||||
settingsInlineMeta: {
|
||||
color: "#8F8F8F",
|
||||
fontSize: 12,
|
||||
fontWeight: "500",
|
||||
},
|
||||
settingsInlineTextActionPressable: {
|
||||
marginLeft: 8,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 2,
|
||||
alignItems: "flex-end",
|
||||
justifyContent: "center",
|
||||
},
|
||||
settingsInlineTextAction: {
|
||||
color: "#D0D0D0",
|
||||
fontSize: 12,
|
||||
fontWeight: "700",
|
||||
minWidth: 72,
|
||||
textAlign: "right",
|
||||
},
|
||||
settingsInlineTextActionInstalled: {
|
||||
color: "#E2B1A8",
|
||||
},
|
||||
settingsInlineTextActionDownloading: {
|
||||
color: "#FFD7CE",
|
||||
},
|
||||
scanRoot: {
|
||||
flex: 1,
|
||||
backgroundColor: "#101014",
|
||||
paddingHorizontal: 16,
|
||||
paddingTop: 12,
|
||||
gap: 12,
|
||||
},
|
||||
scanTop: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
scanTitle: {
|
||||
color: "#E8EAF0",
|
||||
fontSize: 18,
|
||||
fontWeight: "700",
|
||||
},
|
||||
scanClose: {
|
||||
color: "#8FA4CC",
|
||||
fontSize: 15,
|
||||
fontWeight: "600",
|
||||
},
|
||||
scanCam: {
|
||||
flex: 1,
|
||||
borderRadius: 18,
|
||||
overflow: "hidden",
|
||||
},
|
||||
scanEmpty: {
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
paddingHorizontal: 24,
|
||||
},
|
||||
scanHint: {
|
||||
color: "#A6ABBA",
|
||||
fontSize: 14,
|
||||
textAlign: "center",
|
||||
},
|
||||
pairSelectRoot: {
|
||||
flex: 1,
|
||||
backgroundColor: C.bg,
|
||||
paddingHorizontal: 16,
|
||||
paddingTop: 12,
|
||||
},
|
||||
pairSelectTop: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: 12,
|
||||
marginBottom: 8,
|
||||
},
|
||||
pairSelectTitleBlock: {
|
||||
flex: 1,
|
||||
gap: 4,
|
||||
},
|
||||
pairSelectTitle: {
|
||||
color: "#E8EAF0",
|
||||
fontSize: 18,
|
||||
fontWeight: "700",
|
||||
},
|
||||
pairSelectSubtitle: {
|
||||
color: "#A3A3A3",
|
||||
fontSize: 13,
|
||||
fontWeight: "500",
|
||||
},
|
||||
pairSelectClose: {
|
||||
color: "#C5C5C5",
|
||||
fontSize: 15,
|
||||
fontWeight: "600",
|
||||
},
|
||||
pairSelectList: {
|
||||
flex: 1,
|
||||
},
|
||||
pairSelectListContent: {
|
||||
paddingBottom: 12,
|
||||
},
|
||||
pairSelectRow: {
|
||||
minHeight: 74,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: "#242424",
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 10,
|
||||
},
|
||||
pairSelectRowSelected: {
|
||||
backgroundColor: "#171717",
|
||||
},
|
||||
pairSelectRowLast: {
|
||||
borderBottomColor: "#242424",
|
||||
},
|
||||
pairSelectRowMain: {
|
||||
width: "100%",
|
||||
flexDirection: "row",
|
||||
alignItems: "flex-start",
|
||||
justifyContent: "space-between",
|
||||
gap: 12,
|
||||
},
|
||||
pairSelectLeftCol: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
flexDirection: "row",
|
||||
alignItems: "flex-start",
|
||||
gap: 10,
|
||||
},
|
||||
pairSelectDot: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
marginTop: 6,
|
||||
},
|
||||
pairSelectDotChecking: {
|
||||
backgroundColor: "#6F778A",
|
||||
},
|
||||
pairSelectDotOnline: {
|
||||
backgroundColor: AGENT_SUCCESS_GREEN,
|
||||
},
|
||||
pairSelectDotOffline: {
|
||||
backgroundColor: "#E35B5B",
|
||||
},
|
||||
pairSelectRowCopy: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
gap: 2,
|
||||
},
|
||||
pairSelectRowTitleLine: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
},
|
||||
pairSelectHostLabel: {
|
||||
color: "#ECECEC",
|
||||
fontSize: 15,
|
||||
fontWeight: "600",
|
||||
flexShrink: 1,
|
||||
},
|
||||
pairSelectRecommended: {
|
||||
color: "#D5A79F",
|
||||
fontSize: 10,
|
||||
fontWeight: "700",
|
||||
letterSpacing: 0.4,
|
||||
textTransform: "uppercase",
|
||||
},
|
||||
pairSelectHostMeta: {
|
||||
color: "#9F9F9F",
|
||||
fontSize: 12,
|
||||
fontWeight: "500",
|
||||
},
|
||||
pairSelectProbeMeta: {
|
||||
color: "#B8B8B8",
|
||||
fontSize: 12,
|
||||
fontWeight: "500",
|
||||
},
|
||||
pairSelectHostURL: {
|
||||
color: "#7E7E7E",
|
||||
fontSize: 11,
|
||||
fontWeight: "500",
|
||||
},
|
||||
pairSelectLatency: {
|
||||
color: "#D4D4D4",
|
||||
fontSize: 13,
|
||||
fontWeight: "700",
|
||||
minWidth: 76,
|
||||
textAlign: "right",
|
||||
},
|
||||
pairSelectRightCol: {
|
||||
minWidth: 76,
|
||||
flexShrink: 0,
|
||||
alignItems: "flex-end",
|
||||
gap: 8,
|
||||
marginLeft: 10,
|
||||
paddingTop: 2,
|
||||
},
|
||||
pairSelectFooter: {
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: "#242424",
|
||||
paddingTop: 12,
|
||||
paddingBottom: 10,
|
||||
gap: 8,
|
||||
},
|
||||
pairSelectPrimaryButton: {
|
||||
minHeight: BTN_PRIMARY.minHeight,
|
||||
borderRadius: BTN_PRIMARY.borderRadius,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: BTN_PRIMARY.backgroundColor,
|
||||
borderWidth: BTN_PRIMARY.borderWidth,
|
||||
borderColor: BTN_PRIMARY.borderColor,
|
||||
},
|
||||
pairSelectPrimaryButtonDisabled: {
|
||||
opacity: 0.6,
|
||||
},
|
||||
pairSelectPrimaryButtonText: {
|
||||
color: "#FFFFFF",
|
||||
fontSize: 15,
|
||||
fontWeight: "700",
|
||||
},
|
||||
pairSelectSecondaryAction: {
|
||||
color: "#A8A8A8",
|
||||
fontSize: 14,
|
||||
fontWeight: "600",
|
||||
textAlign: "center",
|
||||
paddingVertical: 8,
|
||||
},
|
||||
sendSlot: {
|
||||
height: CONTROL_HEIGHT,
|
||||
overflow: "hidden",
|
||||
},
|
||||
sendButton: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
borderRadius: 20,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: BTN_PRIMARY.backgroundColor,
|
||||
borderWidth: BTN_PRIMARY.borderWidth,
|
||||
borderColor: BTN_PRIMARY.borderColor,
|
||||
},
|
||||
sendButtonDisabled: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
// sendIcon: removed — replaced with SymbolView
|
||||
recordBorder: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
borderRadius: 20,
|
||||
},
|
||||
recordButtonDisabled: {
|
||||
opacity: 0.4,
|
||||
},
|
||||
recordDot: {
|
||||
width: 38,
|
||||
height: 38,
|
||||
backgroundColor: "#FF2E3F",
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
.expoLogoBackground {
|
||||
background-image: linear-gradient(180deg, #3c9ffe, #0274df);
|
||||
border-radius: 40px;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import { Image } from 'expo-image';
|
||||
import { useState } from 'react';
|
||||
import { Dimensions, StyleSheet, View } from 'react-native';
|
||||
import Animated, { Easing, Keyframe } from 'react-native-reanimated';
|
||||
import { scheduleOnRN } from 'react-native-worklets';
|
||||
|
||||
const INITIAL_SCALE_FACTOR = Dimensions.get('screen').height / 90;
|
||||
const DURATION = 600;
|
||||
|
||||
export function AnimatedSplashOverlay() {
|
||||
const [visible, setVisible] = useState(true);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
const splashKeyframe = new Keyframe({
|
||||
0: {
|
||||
transform: [{ scale: INITIAL_SCALE_FACTOR }],
|
||||
opacity: 1,
|
||||
},
|
||||
20: {
|
||||
opacity: 1,
|
||||
},
|
||||
70: {
|
||||
opacity: 0,
|
||||
easing: Easing.elastic(0.7),
|
||||
},
|
||||
100: {
|
||||
opacity: 0,
|
||||
transform: [{ scale: 1 }],
|
||||
easing: Easing.elastic(0.7),
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
entering={splashKeyframe.duration(DURATION).withCallback((finished) => {
|
||||
'worklet';
|
||||
if (finished) {
|
||||
scheduleOnRN(setVisible, false);
|
||||
}
|
||||
})}
|
||||
style={styles.backgroundSolidColor}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const keyframe = new Keyframe({
|
||||
0: {
|
||||
transform: [{ scale: INITIAL_SCALE_FACTOR }],
|
||||
},
|
||||
100: {
|
||||
transform: [{ scale: 1 }],
|
||||
easing: Easing.elastic(0.7),
|
||||
},
|
||||
});
|
||||
|
||||
const logoKeyframe = new Keyframe({
|
||||
0: {
|
||||
transform: [{ scale: 1.3 }],
|
||||
opacity: 0,
|
||||
},
|
||||
40: {
|
||||
transform: [{ scale: 1.3 }],
|
||||
opacity: 0,
|
||||
easing: Easing.elastic(0.7),
|
||||
},
|
||||
100: {
|
||||
opacity: 1,
|
||||
transform: [{ scale: 1 }],
|
||||
easing: Easing.elastic(0.7),
|
||||
},
|
||||
});
|
||||
|
||||
const glowKeyframe = new Keyframe({
|
||||
0: {
|
||||
transform: [{ rotateZ: '0deg' }],
|
||||
},
|
||||
100: {
|
||||
transform: [{ rotateZ: '7200deg' }],
|
||||
},
|
||||
});
|
||||
|
||||
export function AnimatedIcon() {
|
||||
return (
|
||||
<View style={styles.iconContainer}>
|
||||
<Animated.View entering={glowKeyframe.duration(60 * 1000 * 4)} style={styles.glow}>
|
||||
<Image style={styles.glow} source={require('@/assets/images/logo-glow.png')} />
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View entering={keyframe.duration(DURATION)} style={styles.background} />
|
||||
<Animated.View style={styles.imageContainer} entering={logoKeyframe.duration(DURATION)}>
|
||||
<Image style={styles.image} source={require('@/assets/images/expo-logo.png')} />
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
imageContainer: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
glow: {
|
||||
width: 201,
|
||||
height: 201,
|
||||
position: 'absolute',
|
||||
},
|
||||
iconContainer: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: 128,
|
||||
height: 128,
|
||||
zIndex: 100,
|
||||
},
|
||||
image: {
|
||||
position: 'absolute',
|
||||
width: 76,
|
||||
height: 71,
|
||||
},
|
||||
background: {
|
||||
borderRadius: 40,
|
||||
experimental_backgroundImage: `linear-gradient(180deg, #3C9FFE, #0274DF)`,
|
||||
width: 128,
|
||||
height: 128,
|
||||
position: 'absolute',
|
||||
},
|
||||
backgroundSolidColor: {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
backgroundColor: '#208AEF',
|
||||
zIndex: 1000,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
import { Image } from 'expo-image';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
import Animated, { Keyframe, Easing } from 'react-native-reanimated';
|
||||
|
||||
import classes from './animated-icon.module.css';
|
||||
const DURATION = 300;
|
||||
|
||||
export function AnimatedSplashOverlay() {
|
||||
return null;
|
||||
}
|
||||
|
||||
const keyframe = new Keyframe({
|
||||
0: {
|
||||
transform: [{ scale: 0 }],
|
||||
},
|
||||
60: {
|
||||
transform: [{ scale: 1.2 }],
|
||||
easing: Easing.elastic(1.2),
|
||||
},
|
||||
100: {
|
||||
transform: [{ scale: 1 }],
|
||||
easing: Easing.elastic(1.2),
|
||||
},
|
||||
});
|
||||
|
||||
const logoKeyframe = new Keyframe({
|
||||
0: {
|
||||
opacity: 0,
|
||||
},
|
||||
60: {
|
||||
transform: [{ scale: 1.2 }],
|
||||
opacity: 0,
|
||||
easing: Easing.elastic(1.2),
|
||||
},
|
||||
100: {
|
||||
transform: [{ scale: 1 }],
|
||||
opacity: 1,
|
||||
easing: Easing.elastic(1.2),
|
||||
},
|
||||
});
|
||||
|
||||
const glowKeyframe = new Keyframe({
|
||||
0: {
|
||||
transform: [{ rotateZ: '-180deg' }, { scale: 0.8 }],
|
||||
opacity: 0,
|
||||
},
|
||||
[DURATION / 1000]: {
|
||||
transform: [{ rotateZ: '0deg' }, { scale: 1 }],
|
||||
opacity: 1,
|
||||
easing: Easing.elastic(0.7),
|
||||
},
|
||||
100: {
|
||||
transform: [{ rotateZ: '7200deg' }],
|
||||
},
|
||||
});
|
||||
|
||||
export function AnimatedIcon() {
|
||||
return (
|
||||
<View style={styles.iconContainer}>
|
||||
<Animated.View entering={glowKeyframe.duration(60 * 1000 * 4)} style={styles.glow}>
|
||||
<Image style={styles.glow} source={require('@/assets/images/logo-glow.png')} />
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View style={styles.background} entering={keyframe.duration(DURATION)}>
|
||||
<div className={classes.expoLogoBackground} />
|
||||
</Animated.View>
|
||||
|
||||
<Animated.View style={styles.imageContainer} entering={logoKeyframe.duration(DURATION)}>
|
||||
<Image style={styles.image} source={require('@/assets/images/expo-logo.png')} />
|
||||
</Animated.View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
zIndex: 1000,
|
||||
position: 'absolute',
|
||||
top: 128 / 2 + 138,
|
||||
},
|
||||
imageContainer: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
glow: {
|
||||
width: 201,
|
||||
height: 201,
|
||||
position: 'absolute',
|
||||
},
|
||||
iconContainer: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: 128,
|
||||
height: 128,
|
||||
},
|
||||
image: {
|
||||
position: 'absolute',
|
||||
width: 76,
|
||||
height: 71,
|
||||
},
|
||||
background: {
|
||||
width: 128,
|
||||
height: 128,
|
||||
position: 'absolute',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
// Not used - single page app. Kept to avoid breaking template imports.
|
||||
import { Slot } from 'expo-router';
|
||||
import React from 'react';
|
||||
|
||||
export default function AppTabs() {
|
||||
return <Slot />;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Not used - single page app. Kept to avoid breaking template imports.
|
||||
import { Slot } from 'expo-router';
|
||||
import React from 'react';
|
||||
|
||||
export default function AppTabs() {
|
||||
return <Slot />;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Href, Link } from 'expo-router';
|
||||
import { openBrowserAsync, WebBrowserPresentationStyle } from 'expo-web-browser';
|
||||
import { type ComponentProps } from 'react';
|
||||
|
||||
type Props = Omit<ComponentProps<typeof Link>, 'href'> & { href: Href & string };
|
||||
|
||||
export function ExternalLink({ href, ...rest }: Props) {
|
||||
return (
|
||||
<Link
|
||||
target="_blank"
|
||||
{...rest}
|
||||
href={href}
|
||||
onPress={async (event) => {
|
||||
if (process.env.EXPO_OS !== 'web') {
|
||||
// Prevent the default behavior of linking to the default browser on native.
|
||||
event.preventDefault();
|
||||
// Open the link in an in-app browser.
|
||||
await openBrowserAsync(href, {
|
||||
presentationStyle: WebBrowserPresentationStyle.AUTOMATIC,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import React, { type ReactNode } from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
|
||||
import { ThemedText } from './themed-text';
|
||||
import { ThemedView } from './themed-view';
|
||||
|
||||
import { Spacing } from '@/constants/theme';
|
||||
|
||||
type HintRowProps = {
|
||||
title?: string;
|
||||
hint?: ReactNode;
|
||||
};
|
||||
|
||||
export function HintRow({ title = 'Try editing', hint = 'app/index.tsx' }: HintRowProps) {
|
||||
return (
|
||||
<View style={styles.stepRow}>
|
||||
<ThemedText type="small">{title}</ThemedText>
|
||||
<ThemedView type="backgroundSelected" style={styles.codeSnippet}>
|
||||
<ThemedText themeColor="textSecondary">{hint}</ThemedText>
|
||||
</ThemedView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
stepRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
codeSnippet: {
|
||||
borderRadius: Spacing.two,
|
||||
paddingVertical: Spacing.half,
|
||||
paddingHorizontal: Spacing.two,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Platform, StyleSheet, Text, type TextProps } from 'react-native';
|
||||
|
||||
import { Fonts, ThemeColor } from '@/constants/theme';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
|
||||
export type ThemedTextProps = TextProps & {
|
||||
type?: 'default' | 'title' | 'small' | 'smallBold' | 'subtitle' | 'link' | 'linkPrimary' | 'code';
|
||||
themeColor?: ThemeColor;
|
||||
};
|
||||
|
||||
export function ThemedText({ style, type = 'default', themeColor, ...rest }: ThemedTextProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
{ color: theme[themeColor ?? 'text'] },
|
||||
type === 'default' && styles.default,
|
||||
type === 'title' && styles.title,
|
||||
type === 'small' && styles.small,
|
||||
type === 'smallBold' && styles.smallBold,
|
||||
type === 'subtitle' && styles.subtitle,
|
||||
type === 'link' && styles.link,
|
||||
type === 'linkPrimary' && styles.linkPrimary,
|
||||
type === 'code' && styles.code,
|
||||
style,
|
||||
]}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
small: {
|
||||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
fontWeight: 500,
|
||||
},
|
||||
smallBold: {
|
||||
fontSize: 14,
|
||||
lineHeight: 20,
|
||||
fontWeight: 700,
|
||||
},
|
||||
default: {
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
fontWeight: 500,
|
||||
},
|
||||
title: {
|
||||
fontSize: 48,
|
||||
fontWeight: 600,
|
||||
lineHeight: 52,
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 32,
|
||||
lineHeight: 44,
|
||||
fontWeight: 600,
|
||||
},
|
||||
link: {
|
||||
lineHeight: 30,
|
||||
fontSize: 14,
|
||||
},
|
||||
linkPrimary: {
|
||||
lineHeight: 30,
|
||||
fontSize: 14,
|
||||
color: '#3c87f7',
|
||||
},
|
||||
code: {
|
||||
fontFamily: Fonts.mono,
|
||||
fontWeight: Platform.select({ android: 700 }) ?? 500,
|
||||
fontSize: 12,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { View, type ViewProps } from 'react-native';
|
||||
|
||||
import { ThemeColor } from '@/constants/theme';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
|
||||
export type ThemedViewProps = ViewProps & {
|
||||
lightColor?: string;
|
||||
darkColor?: string;
|
||||
type?: ThemeColor;
|
||||
};
|
||||
|
||||
export function ThemedView({ style, lightColor, darkColor, type, ...otherProps }: ThemedViewProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
return <View style={[{ backgroundColor: theme[type ?? 'background'] }, style]} {...otherProps} />;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { SymbolView } from 'expo-symbols';
|
||||
import { PropsWithChildren, useState } from 'react';
|
||||
import { Pressable, StyleSheet } from 'react-native';
|
||||
import Animated, { FadeIn } from 'react-native-reanimated';
|
||||
|
||||
import { ThemedText } from '@/components/themed-text';
|
||||
import { ThemedView } from '@/components/themed-view';
|
||||
import { Spacing } from '@/constants/theme';
|
||||
import { useTheme } from '@/hooks/use-theme';
|
||||
|
||||
export function Collapsible({ children, title }: PropsWithChildren & { title: string }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<ThemedView>
|
||||
<Pressable
|
||||
style={({ pressed }) => [styles.heading, pressed && styles.pressedHeading]}
|
||||
onPress={() => setIsOpen((value) => !value)}>
|
||||
<ThemedView type="backgroundElement" style={styles.button}>
|
||||
<SymbolView
|
||||
name={{ ios: 'chevron.right', android: 'chevron_right', web: 'chevron_right' }}
|
||||
size={14}
|
||||
weight="bold"
|
||||
tintColor={theme.text}
|
||||
style={{ transform: [{ rotate: isOpen ? '-90deg' : '90deg' }] }}
|
||||
/>
|
||||
</ThemedView>
|
||||
|
||||
<ThemedText type="small">{title}</ThemedText>
|
||||
</Pressable>
|
||||
{isOpen && (
|
||||
<Animated.View entering={FadeIn.duration(200)}>
|
||||
<ThemedView type="backgroundElement" style={styles.content}>
|
||||
{children}
|
||||
</ThemedView>
|
||||
</Animated.View>
|
||||
)}
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
heading: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: Spacing.two,
|
||||
},
|
||||
pressedHeading: {
|
||||
opacity: 0.7,
|
||||
},
|
||||
button: {
|
||||
width: Spacing.four,
|
||||
height: Spacing.four,
|
||||
borderRadius: 12,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
content: {
|
||||
marginTop: Spacing.three,
|
||||
borderRadius: Spacing.three,
|
||||
marginLeft: Spacing.four,
|
||||
padding: Spacing.four,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { version } from 'expo/package.json';
|
||||
import { Image } from 'expo-image';
|
||||
import React from 'react';
|
||||
import { useColorScheme, StyleSheet } from 'react-native';
|
||||
|
||||
import { ThemedText } from './themed-text';
|
||||
import { ThemedView } from './themed-view';
|
||||
|
||||
import { Spacing } from '@/constants/theme';
|
||||
|
||||
export function WebBadge() {
|
||||
const scheme = useColorScheme();
|
||||
|
||||
return (
|
||||
<ThemedView style={styles.container}>
|
||||
<ThemedText type="code" themeColor="textSecondary" style={styles.versionText}>
|
||||
v{version}
|
||||
</ThemedText>
|
||||
<Image
|
||||
source={
|
||||
scheme === 'dark'
|
||||
? require('@/assets/images/expo-badge-white.png')
|
||||
: require('@/assets/images/expo-badge.png')
|
||||
}
|
||||
style={styles.badgeImage}
|
||||
/>
|
||||
</ThemedView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
padding: Spacing.five,
|
||||
alignItems: 'center',
|
||||
gap: Spacing.two,
|
||||
},
|
||||
versionText: {
|
||||
textAlign: 'center',
|
||||
},
|
||||
badgeImage: {
|
||||
width: 123,
|
||||
aspectRatio: 123 / 24,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Below are the colors that are used in the app. The colors are defined in the light and dark mode.
|
||||
* There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.
|
||||
*/
|
||||
|
||||
import '@/global.css';
|
||||
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
export const Colors = {
|
||||
light: {
|
||||
text: '#000000',
|
||||
background: '#ffffff',
|
||||
backgroundElement: '#F0F0F3',
|
||||
backgroundSelected: '#E0E1E6',
|
||||
textSecondary: '#60646C',
|
||||
},
|
||||
dark: {
|
||||
text: '#ffffff',
|
||||
background: '#000000',
|
||||
backgroundElement: '#212225',
|
||||
backgroundSelected: '#2E3135',
|
||||
textSecondary: '#B0B4BA',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type ThemeColor = keyof typeof Colors.light & keyof typeof Colors.dark;
|
||||
|
||||
export const Fonts = Platform.select({
|
||||
ios: {
|
||||
/** iOS `UIFontDescriptorSystemDesignDefault` */
|
||||
sans: 'system-ui',
|
||||
/** iOS `UIFontDescriptorSystemDesignSerif` */
|
||||
serif: 'ui-serif',
|
||||
/** iOS `UIFontDescriptorSystemDesignRounded` */
|
||||
rounded: 'ui-rounded',
|
||||
/** iOS `UIFontDescriptorSystemDesignMonospaced` */
|
||||
mono: 'ui-monospace',
|
||||
},
|
||||
default: {
|
||||
sans: 'normal',
|
||||
serif: 'serif',
|
||||
rounded: 'normal',
|
||||
mono: 'monospace',
|
||||
},
|
||||
web: {
|
||||
sans: 'var(--font-display)',
|
||||
serif: 'var(--font-serif)',
|
||||
rounded: 'var(--font-rounded)',
|
||||
mono: 'var(--font-mono)',
|
||||
},
|
||||
});
|
||||
|
||||
export const Spacing = {
|
||||
half: 2,
|
||||
one: 4,
|
||||
two: 8,
|
||||
three: 16,
|
||||
four: 24,
|
||||
five: 32,
|
||||
six: 64,
|
||||
} as const;
|
||||
|
||||
export const BottomTabInset = Platform.select({ ios: 50, android: 80 }) ?? 0;
|
||||
export const MaxContentWidth = 800;
|
||||
@@ -0,0 +1,9 @@
|
||||
:root {
|
||||
--font-display:
|
||||
Spline Sans, Inter, ui-sans-serif, system-ui, sans-serif, Apple Color Emoji, Segoe UI Emoji,
|
||||
Segoe UI Symbol, Noto Color Emoji;
|
||||
--font-mono:
|
||||
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
|
||||
--font-rounded: 'SF Pro Rounded', 'Hiragino Maru Gothic ProN', Meiryo, 'MS PGothic', sans-serif;
|
||||
--font-serif: Georgia, 'Times New Roman', serif;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { useColorScheme } from 'react-native';
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useSyncExternalStore } from "react"
|
||||
import { useColorScheme as useRNColorScheme } from "react-native"
|
||||
|
||||
function subscribe() {
|
||||
return () => {}
|
||||
}
|
||||
|
||||
function getSnapshot() {
|
||||
return true
|
||||
}
|
||||
|
||||
function getServerSnapshot() {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* To support static rendering, this value needs to be re-calculated on the client side for web
|
||||
*/
|
||||
export function useColorScheme() {
|
||||
const hasHydrated = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
|
||||
|
||||
const colorScheme = useRNColorScheme()
|
||||
|
||||
if (hasHydrated) {
|
||||
return colorScheme
|
||||
}
|
||||
|
||||
return "light"
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { Platform } from "react-native"
|
||||
|
||||
type ZeroconfService = {
|
||||
name?: unknown
|
||||
fullName?: unknown
|
||||
host?: unknown
|
||||
port?: unknown
|
||||
addresses?: unknown
|
||||
}
|
||||
|
||||
type ZeroconfInstance = {
|
||||
scan: (type?: string, protocol?: string, domain?: string, implType?: string) => void
|
||||
stop: (implType?: string) => void
|
||||
removeDeviceListeners: () => void
|
||||
getServices: () => Record<string, ZeroconfService>
|
||||
on: (event: string, listener: (...args: unknown[]) => void) => void
|
||||
}
|
||||
|
||||
type ZeroconfModule = {
|
||||
default: new () => ZeroconfInstance
|
||||
ImplType?: {
|
||||
DNSSD?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type DiscoveredServer = {
|
||||
id: string
|
||||
name: string
|
||||
host: string
|
||||
port: number
|
||||
url: string
|
||||
}
|
||||
|
||||
type DiscoveryStatus = "idle" | "scanning" | "error"
|
||||
|
||||
type UseMdnsDiscoveryInput = {
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
function toErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error && error.message.trim().length > 0) {
|
||||
return error.message
|
||||
}
|
||||
|
||||
const next = String(error ?? "")
|
||||
return next.trim().length > 0 ? next : "Unknown discovery error"
|
||||
}
|
||||
|
||||
function cleanHost(input: string): string {
|
||||
const trimmed = input.trim().replace(/\.$/, "")
|
||||
if (!trimmed) return ""
|
||||
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
||||
return trimmed.slice(1, -1)
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
function isIPv4(input: string): boolean {
|
||||
return /^\d{1,3}(?:\.\d{1,3}){3}$/.test(input)
|
||||
}
|
||||
|
||||
function hostTier(input: string): number {
|
||||
if (input.endsWith(".local")) return 0
|
||||
if (isIPv4(input)) {
|
||||
if (input === "127.0.0.1") return 4
|
||||
if (input.startsWith("10.") || input.startsWith("192.168.") || /^172\.(1[6-9]|2\d|3[0-1])\./.test(input)) {
|
||||
return 1
|
||||
}
|
||||
if (/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(input)) {
|
||||
return 1
|
||||
}
|
||||
return 2
|
||||
}
|
||||
if (input.includes(":")) return 3
|
||||
return 2
|
||||
}
|
||||
|
||||
function formatHostForURL(input: string): string {
|
||||
return input.includes(":") ? `[${input}]` : input
|
||||
}
|
||||
|
||||
function isOpenCodeService(service: ZeroconfService): boolean {
|
||||
if (typeof service.name === "string" && service.name.toLowerCase().startsWith("opencode-")) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (typeof service.fullName === "string" && service.fullName.toLowerCase().includes("opencode-")) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function parseService(service: ZeroconfService): DiscoveredServer | null {
|
||||
const port = typeof service.port === "number" ? service.port : Number(service.port)
|
||||
if (!Number.isFinite(port) || port <= 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const hosts = new Set<string>()
|
||||
|
||||
if (typeof service.host === "string") {
|
||||
const host = cleanHost(service.host)
|
||||
if (host.length > 0) {
|
||||
hosts.add(host)
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(service.addresses)) {
|
||||
for (const address of service.addresses) {
|
||||
if (typeof address !== "string") continue
|
||||
const host = cleanHost(address)
|
||||
if (host.length > 0) {
|
||||
hosts.add(host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sortedHosts = [...hosts].sort((a, b) => hostTier(a) - hostTier(b))
|
||||
const host = sortedHosts[0]
|
||||
if (!host) {
|
||||
return null
|
||||
}
|
||||
|
||||
const name = typeof service.name === "string" && service.name.trim().length > 0 ? service.name.trim() : host
|
||||
const fullName =
|
||||
typeof service.fullName === "string" && service.fullName.trim().length > 0
|
||||
? service.fullName.trim()
|
||||
: `${name}:${port}`
|
||||
const url = `http://${formatHostForURL(host)}:${port}`
|
||||
|
||||
return {
|
||||
id: `${fullName}|${url}`,
|
||||
name,
|
||||
host,
|
||||
port,
|
||||
url,
|
||||
}
|
||||
}
|
||||
|
||||
export function useMdnsDiscovery(input: UseMdnsDiscoveryInput) {
|
||||
const [discoveredServers, setDiscoveredServers] = useState<DiscoveredServer[]>([])
|
||||
const [discoveryStatus, setDiscoveryStatus] = useState<DiscoveryStatus>("idle")
|
||||
const [discoveryError, setDiscoveryError] = useState<string | null>(null)
|
||||
const [discoveryAvailable, setDiscoveryAvailable] = useState(Platform.OS !== "web")
|
||||
const startScanRef = useRef<(() => void) | null>(null)
|
||||
|
||||
const refreshDiscovery = useCallback(() => {
|
||||
startScanRef.current?.()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!input.enabled) {
|
||||
startScanRef.current = null
|
||||
setDiscoveredServers([])
|
||||
setDiscoveryStatus("idle")
|
||||
setDiscoveryError(null)
|
||||
return
|
||||
}
|
||||
|
||||
if (Platform.OS === "web") {
|
||||
setDiscoveryAvailable(false)
|
||||
setDiscoveryStatus("idle")
|
||||
setDiscoveryError(null)
|
||||
return
|
||||
}
|
||||
|
||||
let active = true
|
||||
let zeroconf: ZeroconfInstance | null = null
|
||||
let androidImplType: string | undefined
|
||||
|
||||
const rebuildServices = () => {
|
||||
if (!active || !zeroconf) return
|
||||
const values = Object.values(zeroconf.getServices() ?? {})
|
||||
const next = new Map<string, DiscoveredServer>()
|
||||
|
||||
for (const value of values) {
|
||||
if (!isOpenCodeService(value)) continue
|
||||
const parsed = parseService(value)
|
||||
if (!parsed) continue
|
||||
if (!next.has(parsed.url)) {
|
||||
next.set(parsed.url, parsed)
|
||||
}
|
||||
}
|
||||
|
||||
setDiscoveredServers(
|
||||
[...next.values()].sort((a, b) => {
|
||||
const nameOrder = a.name.localeCompare(b.name)
|
||||
if (nameOrder !== 0) return nameOrder
|
||||
return a.url.localeCompare(b.url)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const startScan = () => {
|
||||
if (!active || !zeroconf) return
|
||||
|
||||
try {
|
||||
zeroconf.stop(androidImplType)
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
|
||||
try {
|
||||
zeroconf.scan("http", "tcp", "local.", androidImplType)
|
||||
setDiscoveryStatus("scanning")
|
||||
setDiscoveryError(null)
|
||||
} catch (error) {
|
||||
setDiscoveryStatus("error")
|
||||
setDiscoveryError(toErrorMessage(error))
|
||||
}
|
||||
}
|
||||
|
||||
startScanRef.current = startScan
|
||||
|
||||
try {
|
||||
// Expo dev builds were failing to resolve this native module through async import().
|
||||
const mod = require("react-native-zeroconf") as ZeroconfModule
|
||||
const Zeroconf = mod.default
|
||||
if (typeof Zeroconf !== "function") {
|
||||
setDiscoveryAvailable(false)
|
||||
setDiscoveryStatus("error")
|
||||
setDiscoveryError("mDNS module unavailable")
|
||||
return
|
||||
}
|
||||
|
||||
zeroconf = new Zeroconf()
|
||||
androidImplType = Platform.OS === "android" ? (mod.ImplType?.DNSSD ?? "DNSSD") : undefined
|
||||
setDiscoveryAvailable(true)
|
||||
|
||||
zeroconf.on("resolved", rebuildServices)
|
||||
zeroconf.on("remove", rebuildServices)
|
||||
zeroconf.on("update", rebuildServices)
|
||||
zeroconf.on("error", (error) => {
|
||||
if (!active) return
|
||||
setDiscoveryStatus("error")
|
||||
setDiscoveryError(toErrorMessage(error))
|
||||
})
|
||||
|
||||
startScan()
|
||||
} catch (error) {
|
||||
if (!active) return
|
||||
setDiscoveryAvailable(false)
|
||||
setDiscoveryStatus("error")
|
||||
setDiscoveryError(toErrorMessage(error))
|
||||
}
|
||||
|
||||
return () => {
|
||||
active = false
|
||||
startScanRef.current = null
|
||||
if (!zeroconf) return
|
||||
|
||||
try {
|
||||
zeroconf.stop(androidImplType)
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
|
||||
try {
|
||||
zeroconf.removeDeviceListeners()
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
}, [input.enabled])
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
discoveredServers,
|
||||
discoveryStatus,
|
||||
discoveryError,
|
||||
discoveryAvailable,
|
||||
refreshDiscovery,
|
||||
}),
|
||||
[discoveredServers, discoveryStatus, discoveryError, discoveryAvailable, refreshDiscovery],
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,1051 @@
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type Dispatch,
|
||||
type MutableRefObject,
|
||||
type SetStateAction,
|
||||
} from "react"
|
||||
import { AppState, Platform, type AppStateStatus } from "react-native"
|
||||
import * as Haptics from "expo-haptics"
|
||||
import * as Notifications from "expo-notifications"
|
||||
import Constants from "expo-constants"
|
||||
import { fetch as expoFetch } from "expo/fetch"
|
||||
|
||||
import {
|
||||
classifyMonitorEvent,
|
||||
extractSessionID,
|
||||
formatMonitorEventLabel,
|
||||
type OpenCodeEvent,
|
||||
type MonitorEventType,
|
||||
} from "@/lib/opencode-events"
|
||||
import {
|
||||
parsePendingPermissionRequest,
|
||||
parsePendingPermissionRequests,
|
||||
type PendingPermissionRequest,
|
||||
} from "@/lib/pending-permissions"
|
||||
import { registerRelayDevice, unregisterRelayDevice } from "@/lib/relay-client"
|
||||
import { parseSSEStream } from "@/lib/sse"
|
||||
import { getDevicePushToken, onPushTokenChange } from "@/notifications/monitoring-notifications"
|
||||
import type { ServerItem } from "@/hooks/use-server-sessions"
|
||||
|
||||
export type MonitorJob = {
|
||||
id: string
|
||||
sessionID: string
|
||||
opencodeBaseURL: string
|
||||
startedAt: number
|
||||
}
|
||||
|
||||
export type PermissionDecision = "once" | "always" | "reject"
|
||||
|
||||
export type PromptHistoryEntry = {
|
||||
promptText: string
|
||||
userMessageID: string
|
||||
}
|
||||
|
||||
type SessionRuntimeStatus = "idle" | "busy" | "retry"
|
||||
|
||||
type PermissionPromptState = "idle" | "pending" | "granted" | "denied"
|
||||
|
||||
type NotificationPayload = {
|
||||
serverID: string | null
|
||||
eventType: MonitorEventType | null
|
||||
sessionID: string | null
|
||||
}
|
||||
|
||||
type CuePlayer = {
|
||||
seekTo: (position: number) => unknown
|
||||
play: () => unknown
|
||||
}
|
||||
|
||||
type UseMonitoringOptions = {
|
||||
completePlayer: CuePlayer
|
||||
closeDropdown: () => void
|
||||
findServerForSession: (sessionID: string, preferredServerID?: string | null) => Promise<ServerItem | null>
|
||||
refreshServerStatusAndSessions: (serverID: string, includeSessions?: boolean) => Promise<void>
|
||||
servers: ServerItem[]
|
||||
serversRef: MutableRefObject<ServerItem[]>
|
||||
restoredRef: MutableRefObject<boolean>
|
||||
activeServerId: string | null
|
||||
activeSessionId: string | null
|
||||
activeServerIdRef: MutableRefObject<string | null>
|
||||
activeSessionIdRef: MutableRefObject<string | null>
|
||||
setActiveServerId: Dispatch<SetStateAction<string | null>>
|
||||
setActiveSessionId: Dispatch<SetStateAction<string | null>>
|
||||
setAgentStateDismissed: Dispatch<SetStateAction<boolean>>
|
||||
setNotificationPermissionState: Dispatch<SetStateAction<PermissionPromptState>>
|
||||
}
|
||||
|
||||
function parseMonitorEventType(value: unknown): MonitorEventType | null {
|
||||
if (value === "complete" || value === "permission" || value === "error") {
|
||||
return value
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function parseNotificationPayload(data: unknown): NotificationPayload | null {
|
||||
if (!data || typeof data !== "object") return null
|
||||
|
||||
const serverIDRaw = (data as { serverID?: unknown }).serverID
|
||||
const serverID = typeof serverIDRaw === "string" && serverIDRaw.length > 0 ? serverIDRaw : null
|
||||
|
||||
const eventType = parseMonitorEventType((data as { eventType?: unknown }).eventType)
|
||||
const sessionIDRaw = (data as { sessionID?: unknown }).sessionID
|
||||
const sessionID = typeof sessionIDRaw === "string" && sessionIDRaw.length > 0 ? sessionIDRaw : null
|
||||
|
||||
if (!eventType && !sessionID && !serverID) return null
|
||||
|
||||
return {
|
||||
serverID,
|
||||
eventType,
|
||||
sessionID,
|
||||
}
|
||||
}
|
||||
|
||||
export function useMonitoring({
|
||||
completePlayer,
|
||||
closeDropdown,
|
||||
findServerForSession,
|
||||
refreshServerStatusAndSessions,
|
||||
servers,
|
||||
serversRef,
|
||||
restoredRef,
|
||||
activeServerId,
|
||||
activeSessionId,
|
||||
activeServerIdRef,
|
||||
activeSessionIdRef,
|
||||
setActiveServerId,
|
||||
setActiveSessionId,
|
||||
setAgentStateDismissed,
|
||||
setNotificationPermissionState,
|
||||
}: UseMonitoringOptions) {
|
||||
const [devicePushToken, setDevicePushToken] = useState<string | null>(null)
|
||||
const [monitorJob, setMonitorJob] = useState<MonitorJob | null>(null)
|
||||
const [monitorStatus, setMonitorStatus] = useState("")
|
||||
const [latestAssistantResponse, setLatestAssistantResponse] = useState("")
|
||||
const [latestPromptText, setLatestPromptText] = useState("")
|
||||
const [promptHistory, setPromptHistory] = useState<PromptHistoryEntry[]>([])
|
||||
const [latestAssistantContext, setLatestAssistantContext] = useState<LatestAssistantContext | null>(null)
|
||||
const [pendingPermissions, setPendingPermissions] = useState<PendingPermissionRequest[]>([])
|
||||
const [replyingPermissionID, setReplyingPermissionID] = useState<string | null>(null)
|
||||
const [appState, setAppState] = useState<AppStateStatus>(AppState.currentState)
|
||||
|
||||
const foregroundMonitorAbortRef = useRef<AbortController | null>(null)
|
||||
const foregroundPollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
const monitorJobRef = useRef<MonitorJob | null>(null)
|
||||
const syncSessionStateRef = useRef<
|
||||
((input: { serverID: string; sessionID: string; preserveStatusLabel?: boolean }) => Promise<void>) | null
|
||||
>(null)
|
||||
const pendingNotificationEventsRef = useRef<{ payload: NotificationPayload; source: "received" | "response" }[]>([])
|
||||
const notificationHandlerRef = useRef<(payload: NotificationPayload, source: "received" | "response") => void>(
|
||||
(payload, source) => {
|
||||
pendingNotificationEventsRef.current.push({ payload, source })
|
||||
},
|
||||
)
|
||||
const previousPushTokenRef = useRef<string | null>(null)
|
||||
const previousAppStateRef = useRef<AppStateStatus>(AppState.currentState)
|
||||
const latestAssistantRequestRef = useRef(0)
|
||||
const latestPermissionRequestRef = useRef(0)
|
||||
|
||||
const upsertPendingPermission = useCallback(
|
||||
(request: PendingPermissionRequest) => {
|
||||
setPendingPermissions((current) => {
|
||||
const next = current.filter((item) => item.id !== request.id)
|
||||
return [request, ...next]
|
||||
})
|
||||
closeDropdown()
|
||||
setAgentStateDismissed(false)
|
||||
},
|
||||
[closeDropdown, setAgentStateDismissed],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
monitorJobRef.current = monitorJob
|
||||
}, [monitorJob])
|
||||
|
||||
useEffect(() => {
|
||||
const sub = AppState.addEventListener("change", (nextState) => {
|
||||
setAppState(nextState)
|
||||
})
|
||||
return () => sub.remove()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
let active = true
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
if (Platform.OS !== "ios") return
|
||||
const existing = await Notifications.getPermissionsAsync()
|
||||
const granted = Boolean((existing as { granted?: unknown }).granted)
|
||||
if (active) {
|
||||
setNotificationPermissionState(granted ? "granted" : "idle")
|
||||
}
|
||||
if (!granted) return
|
||||
const token = await getDevicePushToken()
|
||||
if (token) {
|
||||
setDevicePushToken(token)
|
||||
}
|
||||
} catch {
|
||||
// Non-fatal: monitoring can still work in-app via foreground SSE.
|
||||
}
|
||||
})()
|
||||
|
||||
const sub = onPushTokenChange((token) => {
|
||||
if (!active) return
|
||||
setDevicePushToken(token)
|
||||
})
|
||||
|
||||
return () => {
|
||||
active = false
|
||||
sub.remove()
|
||||
}
|
||||
}, [setNotificationPermissionState])
|
||||
|
||||
useEffect(() => {
|
||||
const notificationSub = Notifications.addNotificationReceivedListener((notification: unknown) => {
|
||||
const data = (notification as { request?: { content?: { data?: unknown } } }).request?.content?.data
|
||||
const payload = parseNotificationPayload(data)
|
||||
if (!payload) return
|
||||
notificationHandlerRef.current(payload, "received")
|
||||
})
|
||||
|
||||
const responseSub = Notifications.addNotificationResponseReceivedListener((response: unknown) => {
|
||||
const data = (response as { notification?: { request?: { content?: { data?: unknown } } } }).notification?.request
|
||||
?.content?.data
|
||||
const payload = parseNotificationPayload(data)
|
||||
if (!payload) return
|
||||
notificationHandlerRef.current(payload, "response")
|
||||
})
|
||||
|
||||
void Notifications.getLastNotificationResponseAsync()
|
||||
.then((response) => {
|
||||
if (!response) return
|
||||
const data = (response as { notification?: { request?: { content?: { data?: unknown } } } }).notification
|
||||
?.request?.content?.data
|
||||
const payload = parseNotificationPayload(data)
|
||||
if (!payload) return
|
||||
notificationHandlerRef.current(payload, "response")
|
||||
})
|
||||
.catch(() => {})
|
||||
|
||||
return () => {
|
||||
notificationSub.remove()
|
||||
responseSub.remove()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const stopForegroundMonitor = useCallback(() => {
|
||||
const aborter = foregroundMonitorAbortRef.current
|
||||
if (aborter) {
|
||||
aborter.abort()
|
||||
foregroundMonitorAbortRef.current = null
|
||||
}
|
||||
if (foregroundPollIntervalRef.current) {
|
||||
clearInterval(foregroundPollIntervalRef.current)
|
||||
foregroundPollIntervalRef.current = null
|
||||
}
|
||||
}, [])
|
||||
|
||||
const loadLatestAssistantResponse = useCallback(
|
||||
async (baseURL: string, sessionID: string) => {
|
||||
const requestID = latestAssistantRequestRef.current + 1
|
||||
latestAssistantRequestRef.current = requestID
|
||||
|
||||
const base = baseURL.replace(/\/+$/, "")
|
||||
|
||||
try {
|
||||
const response = await fetch(`${base}/session/${sessionID}/message?limit=60`)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Session messages failed (${response.status})`)
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as unknown
|
||||
const latest = findLatestAssistantCompletion(payload)
|
||||
const promptText = findLatestUserPrompt(payload)
|
||||
const history = buildPromptHistory(payload)
|
||||
|
||||
if (latestAssistantRequestRef.current !== requestID) return
|
||||
if (activeSessionIdRef.current !== sessionID) return
|
||||
setLatestAssistantResponse(latest.text)
|
||||
setLatestPromptText(promptText)
|
||||
setPromptHistory(history)
|
||||
setLatestAssistantContext(latest.context)
|
||||
if (latest.text) {
|
||||
setAgentStateDismissed(false)
|
||||
}
|
||||
} catch {
|
||||
if (latestAssistantRequestRef.current !== requestID) return
|
||||
if (activeSessionIdRef.current !== sessionID) return
|
||||
setLatestAssistantResponse("")
|
||||
setLatestPromptText("")
|
||||
setPromptHistory([])
|
||||
setLatestAssistantContext(null)
|
||||
}
|
||||
},
|
||||
[activeSessionIdRef, setAgentStateDismissed],
|
||||
)
|
||||
|
||||
const loadPendingPermissions = useCallback(
|
||||
async (baseURL: string, sessionID: string) => {
|
||||
const requestID = latestPermissionRequestRef.current + 1
|
||||
latestPermissionRequestRef.current = requestID
|
||||
|
||||
const base = baseURL.replace(/\/+$/, "")
|
||||
|
||||
try {
|
||||
const response = await fetch(`${base}/permission`)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Permission list failed (${response.status})`)
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as unknown
|
||||
const requests = parsePendingPermissionRequests(payload).filter((item) => item.sessionID === sessionID)
|
||||
|
||||
if (latestPermissionRequestRef.current !== requestID) return
|
||||
if (activeSessionIdRef.current !== sessionID) return
|
||||
|
||||
setPendingPermissions(requests)
|
||||
if (requests.length > 0) {
|
||||
closeDropdown()
|
||||
setAgentStateDismissed(false)
|
||||
}
|
||||
} catch {
|
||||
if (latestPermissionRequestRef.current !== requestID) return
|
||||
if (activeSessionIdRef.current !== sessionID) return
|
||||
}
|
||||
},
|
||||
[activeSessionIdRef, closeDropdown, setAgentStateDismissed],
|
||||
)
|
||||
|
||||
const fetchSessionRuntimeStatus = useCallback(
|
||||
async (baseURL: string, sessionID: string): Promise<SessionRuntimeStatus | null> => {
|
||||
const base = baseURL.replace(/\/+$/, "")
|
||||
|
||||
try {
|
||||
const response = await fetch(`${base}/session/status`)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Session status failed (${response.status})`)
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as unknown
|
||||
if (!payload || typeof payload !== "object") return null
|
||||
|
||||
const status = (payload as Record<string, unknown>)[sessionID]
|
||||
if (!status || typeof status !== "object") return "idle"
|
||||
|
||||
const type = (status as { type?: unknown }).type
|
||||
if (type === "busy" || type === "retry" || type === "idle") {
|
||||
return type
|
||||
}
|
||||
|
||||
return null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
const handleMonitorEvent = useCallback(
|
||||
(eventType: MonitorEventType, job: MonitorJob) => {
|
||||
setMonitorStatus(formatMonitorEventLabel(eventType))
|
||||
|
||||
if (eventType === "permission") {
|
||||
void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning).catch(() => {})
|
||||
void loadPendingPermissions(job.opencodeBaseURL, job.sessionID)
|
||||
return
|
||||
}
|
||||
|
||||
if (eventType === "complete") {
|
||||
void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {})
|
||||
void completePlayer.seekTo(0)
|
||||
void completePlayer.play()
|
||||
stopForegroundMonitor()
|
||||
setMonitorJob(null)
|
||||
void loadLatestAssistantResponse(job.opencodeBaseURL, job.sessionID)
|
||||
return
|
||||
}
|
||||
|
||||
void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {})
|
||||
stopForegroundMonitor()
|
||||
setMonitorJob(null)
|
||||
},
|
||||
[completePlayer, loadLatestAssistantResponse, loadPendingPermissions, stopForegroundMonitor],
|
||||
)
|
||||
|
||||
const startForegroundMonitor = useCallback(
|
||||
(job: MonitorJob) => {
|
||||
stopForegroundMonitor()
|
||||
|
||||
const abortController = new AbortController()
|
||||
foregroundMonitorAbortRef.current = abortController
|
||||
|
||||
const base = job.opencodeBaseURL.replace(/\/+$/, "")
|
||||
|
||||
// SSE stream with automatic recovery on failure or natural close
|
||||
const connectSSE = () => {
|
||||
void (async () => {
|
||||
try {
|
||||
const response = await expoFetch(`${base}/event`, {
|
||||
signal: abortController.signal,
|
||||
headers: {
|
||||
Accept: "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok || !response.body) {
|
||||
throw new Error(`SSE monitor failed (${response.status})`)
|
||||
}
|
||||
|
||||
for await (const message of parseSSEStream(response.body)) {
|
||||
let parsed: OpenCodeEvent | null = null
|
||||
try {
|
||||
parsed = JSON.parse(message.data) as OpenCodeEvent
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!parsed) continue
|
||||
const sessionID = extractSessionID(parsed)
|
||||
if (sessionID !== job.sessionID) continue
|
||||
|
||||
if (parsed.type === "permission.asked") {
|
||||
const request = parsePendingPermissionRequest(parsed.properties)
|
||||
if (request) {
|
||||
upsertPendingPermission(request)
|
||||
}
|
||||
}
|
||||
|
||||
const eventType = classifyMonitorEvent(parsed)
|
||||
if (!eventType) continue
|
||||
|
||||
const active = monitorJobRef.current
|
||||
if (!active || active.id !== job.id) return
|
||||
handleMonitorEvent(eventType, job)
|
||||
}
|
||||
|
||||
// Stream ended naturally (server closed connection) -- fall through to recovery
|
||||
} catch {
|
||||
if (abortController.signal.aborted) return
|
||||
// SSE failed (network drop, server restart, etc.) -- fall through to recovery
|
||||
}
|
||||
|
||||
// Recovery: if this job is still active and we weren't explicitly aborted, poll session status
|
||||
if (abortController.signal.aborted) return
|
||||
const active = monitorJobRef.current
|
||||
if (!active || active.id !== job.id) return
|
||||
|
||||
const serverID = activeServerIdRef.current
|
||||
const sessionID = activeSessionIdRef.current
|
||||
if (serverID && sessionID) {
|
||||
void syncSessionStateRef.current?.({ serverID, sessionID })
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
connectSSE()
|
||||
|
||||
// Periodic polling fallback: check session status every 20s in case SSE silently drops
|
||||
foregroundPollIntervalRef.current = setInterval(() => {
|
||||
const active = monitorJobRef.current
|
||||
if (!active || active.id !== job.id) {
|
||||
if (foregroundPollIntervalRef.current) {
|
||||
clearInterval(foregroundPollIntervalRef.current)
|
||||
foregroundPollIntervalRef.current = null
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const serverID = activeServerIdRef.current
|
||||
const sessionID = activeSessionIdRef.current
|
||||
if (serverID && sessionID) {
|
||||
void syncSessionStateRef.current?.({ serverID, sessionID, preserveStatusLabel: true })
|
||||
}
|
||||
}, 20_000)
|
||||
},
|
||||
[activeServerIdRef, activeSessionIdRef, handleMonitorEvent, stopForegroundMonitor, upsertPendingPermission],
|
||||
)
|
||||
|
||||
const beginMonitoring = useCallback(
|
||||
async (job: MonitorJob) => {
|
||||
setMonitorJob(job)
|
||||
setMonitorStatus("Monitoring…")
|
||||
startForegroundMonitor(job)
|
||||
},
|
||||
[startForegroundMonitor],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const active = monitorJobRef.current
|
||||
if (!active) return
|
||||
|
||||
if (appState === "active") {
|
||||
startForegroundMonitor(active)
|
||||
return
|
||||
}
|
||||
|
||||
stopForegroundMonitor()
|
||||
}, [appState, startForegroundMonitor, stopForegroundMonitor])
|
||||
|
||||
useEffect(() => {
|
||||
const active = monitorJobRef.current
|
||||
if (!active) return
|
||||
if (activeSessionId === active.sessionID) return
|
||||
|
||||
stopForegroundMonitor()
|
||||
setMonitorJob(null)
|
||||
setMonitorStatus("")
|
||||
}, [activeSessionId, stopForegroundMonitor])
|
||||
|
||||
useEffect(() => {
|
||||
setLatestAssistantResponse("")
|
||||
setLatestPromptText("")
|
||||
setPromptHistory([])
|
||||
setLatestAssistantContext(null)
|
||||
setPendingPermissions([])
|
||||
setAgentStateDismissed(false)
|
||||
if (!activeServerId || !activeSessionId) return
|
||||
|
||||
const server = serversRef.current.find((item) => item.id === activeServerId)
|
||||
if (!server || server.status !== "online") return
|
||||
void loadLatestAssistantResponse(server.url, activeSessionId)
|
||||
void loadPendingPermissions(server.url, activeSessionId)
|
||||
}, [
|
||||
activeServerId,
|
||||
activeSessionId,
|
||||
loadLatestAssistantResponse,
|
||||
loadPendingPermissions,
|
||||
serversRef,
|
||||
setAgentStateDismissed,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
stopForegroundMonitor()
|
||||
}
|
||||
}, [stopForegroundMonitor])
|
||||
|
||||
const syncSessionState = useCallback(
|
||||
async (input: { serverID: string; sessionID: string; preserveStatusLabel?: boolean }) => {
|
||||
await refreshServerStatusAndSessions(input.serverID)
|
||||
|
||||
const server = serversRef.current.find((item) => item.id === input.serverID)
|
||||
if (!server || server.status !== "online") return
|
||||
|
||||
const runtimeStatus = await fetchSessionRuntimeStatus(server.url, input.sessionID)
|
||||
await loadLatestAssistantResponse(server.url, input.sessionID)
|
||||
await loadPendingPermissions(server.url, input.sessionID)
|
||||
|
||||
if (runtimeStatus === "busy" || runtimeStatus === "retry") {
|
||||
const nextJob: MonitorJob = {
|
||||
id: `job-resume-${Date.now()}`,
|
||||
sessionID: input.sessionID,
|
||||
opencodeBaseURL: server.url.replace(/\/+$/, ""),
|
||||
startedAt: Date.now(),
|
||||
}
|
||||
|
||||
setMonitorJob(nextJob)
|
||||
setMonitorStatus("Monitoring…")
|
||||
if (appState === "active") {
|
||||
startForegroundMonitor(nextJob)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (runtimeStatus === "idle") {
|
||||
stopForegroundMonitor()
|
||||
setMonitorJob(null)
|
||||
if (!input.preserveStatusLabel) {
|
||||
setMonitorStatus("")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// runtimeStatus is null (fetch failed or unparseable) -- retry after a short delay
|
||||
// if a monitor job is still active, so we don't leave the user stuck
|
||||
if (runtimeStatus === null && monitorJobRef.current) {
|
||||
setTimeout(() => {
|
||||
const serverID = activeServerIdRef.current
|
||||
const sessionID = activeSessionIdRef.current
|
||||
if (serverID && sessionID && monitorJobRef.current) {
|
||||
void syncSessionStateRef.current?.({ serverID, sessionID })
|
||||
}
|
||||
}, 5_000)
|
||||
}
|
||||
},
|
||||
[
|
||||
activeServerIdRef,
|
||||
activeSessionIdRef,
|
||||
appState,
|
||||
fetchSessionRuntimeStatus,
|
||||
loadLatestAssistantResponse,
|
||||
loadPendingPermissions,
|
||||
refreshServerStatusAndSessions,
|
||||
serversRef,
|
||||
startForegroundMonitor,
|
||||
stopForegroundMonitor,
|
||||
],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
syncSessionStateRef.current = syncSessionState
|
||||
}, [syncSessionState])
|
||||
|
||||
const handleNotificationPayload = useCallback(
|
||||
async (payload: NotificationPayload, source: "received" | "response") => {
|
||||
const activeServer = activeServerIdRef.current
|
||||
? serversRef.current.find((server) => server.id === activeServerIdRef.current)
|
||||
: null
|
||||
const matchesActiveSession =
|
||||
!!payload.sessionID &&
|
||||
activeSessionIdRef.current === payload.sessionID &&
|
||||
(!payload.serverID || activeServer?.serverID === payload.serverID)
|
||||
|
||||
if (payload.eventType && (source === "response" || matchesActiveSession || !payload.sessionID)) {
|
||||
setMonitorStatus(formatMonitorEventLabel(payload.eventType))
|
||||
}
|
||||
|
||||
if (payload.eventType === "complete" && source === "received") {
|
||||
void completePlayer.seekTo(0)
|
||||
void completePlayer.play()
|
||||
}
|
||||
|
||||
if (
|
||||
(payload.eventType === "complete" || payload.eventType === "error") &&
|
||||
(source === "response" || matchesActiveSession)
|
||||
) {
|
||||
stopForegroundMonitor()
|
||||
setMonitorJob(null)
|
||||
}
|
||||
|
||||
if (!payload.sessionID) return
|
||||
|
||||
if (source === "response") {
|
||||
const matched = await findServerForSession(payload.sessionID, payload.serverID)
|
||||
if (!matched) {
|
||||
console.log("[Notification] open:session-not-found", {
|
||||
serverID: payload.serverID,
|
||||
sessionID: payload.sessionID,
|
||||
eventType: payload.eventType,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
activeServerIdRef.current = matched.id
|
||||
activeSessionIdRef.current = payload.sessionID
|
||||
setActiveServerId(matched.id)
|
||||
setActiveSessionId(payload.sessionID)
|
||||
closeDropdown()
|
||||
setAgentStateDismissed(false)
|
||||
|
||||
await syncSessionState({
|
||||
serverID: matched.id,
|
||||
sessionID: payload.sessionID,
|
||||
preserveStatusLabel: Boolean(payload.eventType),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!matchesActiveSession) return
|
||||
|
||||
const activeServerID = activeServerIdRef.current
|
||||
if (!activeServerID) return
|
||||
|
||||
await syncSessionState({
|
||||
serverID: activeServerID,
|
||||
sessionID: payload.sessionID,
|
||||
preserveStatusLabel: Boolean(payload.eventType),
|
||||
})
|
||||
},
|
||||
[
|
||||
activeServerIdRef,
|
||||
activeSessionIdRef,
|
||||
closeDropdown,
|
||||
completePlayer,
|
||||
findServerForSession,
|
||||
serversRef,
|
||||
setActiveServerId,
|
||||
setActiveSessionId,
|
||||
setAgentStateDismissed,
|
||||
stopForegroundMonitor,
|
||||
syncSessionState,
|
||||
],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
notificationHandlerRef.current = (payload, source) => {
|
||||
void handleNotificationPayload(payload, source)
|
||||
}
|
||||
|
||||
if (!pendingNotificationEventsRef.current.length) return
|
||||
|
||||
const queued = [...pendingNotificationEventsRef.current]
|
||||
pendingNotificationEventsRef.current = []
|
||||
queued.forEach(({ payload, source }) => {
|
||||
void handleNotificationPayload(payload, source)
|
||||
})
|
||||
}, [handleNotificationPayload])
|
||||
|
||||
useEffect(() => {
|
||||
const previous = previousAppStateRef.current
|
||||
previousAppStateRef.current = appState
|
||||
|
||||
if (appState !== "active" || previous === "active") return
|
||||
|
||||
const serverID = activeServerIdRef.current
|
||||
const sessionID = activeSessionIdRef.current
|
||||
if (!serverID || !sessionID) return
|
||||
|
||||
void syncSessionState({ serverID, sessionID })
|
||||
}, [activeServerIdRef, activeSessionIdRef, appState, syncSessionState])
|
||||
|
||||
const respondToPermission = useCallback(
|
||||
async (input: { serverID: string; sessionID: string; requestID: string; reply: PermissionDecision }) => {
|
||||
const server = serversRef.current.find((item) => item.id === input.serverID)
|
||||
if (!server) {
|
||||
throw new Error("Server unavailable")
|
||||
}
|
||||
|
||||
const base = server.url.replace(/\/+$/, "")
|
||||
setReplyingPermissionID(input.requestID)
|
||||
setMonitorStatus(input.reply === "reject" ? "Rejecting request…" : "Sending approval…")
|
||||
let removed: PendingPermissionRequest | undefined
|
||||
setPendingPermissions((current) => {
|
||||
removed = current.find((item) => item.id === input.requestID)
|
||||
return current.filter((item) => item.id !== input.requestID)
|
||||
})
|
||||
|
||||
try {
|
||||
const response = await fetch(`${base}/permission/${input.requestID}/reply`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ reply: input.reply }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Permission reply failed (${response.status})`)
|
||||
}
|
||||
|
||||
await syncSessionState({
|
||||
serverID: input.serverID,
|
||||
sessionID: input.sessionID,
|
||||
})
|
||||
} catch (error) {
|
||||
if (removed) {
|
||||
setPendingPermissions((current) => {
|
||||
const restored = removed
|
||||
if (!restored) {
|
||||
return current
|
||||
}
|
||||
if (current.some((item) => item.id === restored.id)) {
|
||||
return current
|
||||
}
|
||||
return [restored, ...current]
|
||||
})
|
||||
}
|
||||
throw error
|
||||
} finally {
|
||||
setReplyingPermissionID((current) => (current === input.requestID ? null : current))
|
||||
}
|
||||
},
|
||||
[serversRef, syncSessionState],
|
||||
)
|
||||
|
||||
const activePermissionRequest = pendingPermissions[0] ?? null
|
||||
|
||||
const relayServersKey = useMemo(
|
||||
() =>
|
||||
servers
|
||||
.filter((server) => server.relaySecret.trim().length > 0)
|
||||
.map((server) => `${server.id}:${server.relayURL}:${server.relaySecret.trim()}`)
|
||||
.join("|"),
|
||||
[servers],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (Platform.OS !== "ios") return
|
||||
if (!devicePushToken) return
|
||||
|
||||
const list = serversRef.current.filter((server) => server.relaySecret.trim().length > 0)
|
||||
if (!list.length) return
|
||||
|
||||
const bundleId = Constants.expoConfig?.ios?.bundleIdentifier ?? "com.anomalyco.mobilevoice"
|
||||
const apnsEnv = "production"
|
||||
console.log("[Relay] env", {
|
||||
dev: __DEV__,
|
||||
node: process.env.NODE_ENV,
|
||||
apnsEnv,
|
||||
})
|
||||
console.log("[Relay] register:batch", {
|
||||
tokenSuffix: devicePushToken.slice(-8),
|
||||
count: list.length,
|
||||
apnsEnv,
|
||||
bundleId,
|
||||
})
|
||||
|
||||
void Promise.allSettled(
|
||||
list.map(async (server) => {
|
||||
const secret = server.relaySecret.trim()
|
||||
const relay = server.relayURL
|
||||
console.log("[Relay] register:start", {
|
||||
id: server.id,
|
||||
relay,
|
||||
tokenSuffix: devicePushToken.slice(-8),
|
||||
secretLength: secret.length,
|
||||
})
|
||||
try {
|
||||
await registerRelayDevice({
|
||||
relayBaseURL: relay,
|
||||
secret,
|
||||
deviceToken: devicePushToken,
|
||||
bundleId,
|
||||
apnsEnv,
|
||||
})
|
||||
console.log("[Relay] register:ok", { id: server.id, relay })
|
||||
} catch (err) {
|
||||
console.log("[Relay] register:error", {
|
||||
id: server.id,
|
||||
relay,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
}
|
||||
}),
|
||||
).catch(() => {})
|
||||
}, [devicePushToken, relayServersKey, serversRef])
|
||||
|
||||
useEffect(() => {
|
||||
if (Platform.OS !== "ios") return
|
||||
if (!devicePushToken) return
|
||||
const previous = previousPushTokenRef.current
|
||||
previousPushTokenRef.current = devicePushToken
|
||||
if (!previous || previous === devicePushToken) return
|
||||
|
||||
const list = serversRef.current.filter((server) => server.relaySecret.trim().length > 0)
|
||||
if (!list.length) return
|
||||
console.log("[Relay] unregister:batch", {
|
||||
previousSuffix: previous.slice(-8),
|
||||
nextSuffix: devicePushToken.slice(-8),
|
||||
count: list.length,
|
||||
})
|
||||
|
||||
void Promise.allSettled(
|
||||
list.map(async (server) => {
|
||||
const secret = server.relaySecret.trim()
|
||||
const relay = server.relayURL
|
||||
console.log("[Relay] unregister:start", {
|
||||
id: server.id,
|
||||
relay,
|
||||
tokenSuffix: previous.slice(-8),
|
||||
secretLength: secret.length,
|
||||
})
|
||||
try {
|
||||
await unregisterRelayDevice({
|
||||
relayBaseURL: relay,
|
||||
secret,
|
||||
deviceToken: previous,
|
||||
})
|
||||
console.log("[Relay] unregister:ok", { id: server.id, relay })
|
||||
} catch (err) {
|
||||
console.log("[Relay] unregister:error", {
|
||||
id: server.id,
|
||||
relay,
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
})
|
||||
}
|
||||
}),
|
||||
).catch(() => {})
|
||||
}, [devicePushToken, relayServersKey, serversRef])
|
||||
|
||||
return {
|
||||
devicePushToken,
|
||||
setDevicePushToken,
|
||||
monitorJob,
|
||||
monitorStatus,
|
||||
setMonitorStatus,
|
||||
latestPromptText,
|
||||
setLatestPromptText,
|
||||
promptHistory,
|
||||
setPromptHistory,
|
||||
latestAssistantResponse,
|
||||
latestAssistantContext,
|
||||
activePermissionRequest,
|
||||
pendingPermissionCount: pendingPermissions.length,
|
||||
respondingPermissionID: replyingPermissionID,
|
||||
respondToPermission,
|
||||
beginMonitoring,
|
||||
}
|
||||
}
|
||||
|
||||
type SessionMessageInfo = {
|
||||
role?: unknown
|
||||
time?: unknown
|
||||
modelID?: unknown
|
||||
providerID?: unknown
|
||||
path?: unknown
|
||||
agent?: unknown
|
||||
}
|
||||
|
||||
type SessionMessagePart = {
|
||||
type?: unknown
|
||||
text?: unknown
|
||||
}
|
||||
|
||||
type SessionMessagePayload = {
|
||||
info?: unknown
|
||||
parts?: unknown
|
||||
}
|
||||
|
||||
type LatestAssistantContext = {
|
||||
providerID: string | null
|
||||
modelID: string | null
|
||||
workingDirectory: string | null
|
||||
agent: string | null
|
||||
}
|
||||
|
||||
type LatestAssistantSnapshot = {
|
||||
text: string
|
||||
context: LatestAssistantContext | null
|
||||
}
|
||||
|
||||
function cleanTranscriptText(text: string): string {
|
||||
return text.replace(/[ \t]+$/gm, "").trimEnd()
|
||||
}
|
||||
|
||||
function cleanSessionText(text: string): string {
|
||||
return cleanTranscriptText(text).trimStart()
|
||||
}
|
||||
|
||||
function extractMessageText(parts: SessionMessagePart[]): string {
|
||||
const textParts: string[] = []
|
||||
|
||||
for (const part of parts) {
|
||||
if (!part || part.type !== "text" || typeof part.text !== "string") continue
|
||||
|
||||
const text = cleanSessionText(part.text)
|
||||
if (text.length > 0) {
|
||||
textParts.push(text)
|
||||
}
|
||||
}
|
||||
|
||||
return textParts.join("\n\n")
|
||||
}
|
||||
|
||||
function maybeString(value: unknown): string | null {
|
||||
if (typeof value !== "string") return null
|
||||
const trimmed = value.trim()
|
||||
return trimmed.length > 0 ? trimmed : null
|
||||
}
|
||||
|
||||
function buildPromptHistory(payload: unknown): PromptHistoryEntry[] {
|
||||
if (!Array.isArray(payload)) return []
|
||||
|
||||
const entries: PromptHistoryEntry[] = []
|
||||
|
||||
for (const candidate of payload) {
|
||||
const msg = candidate as SessionMessagePayload
|
||||
if (!msg || typeof msg !== "object") continue
|
||||
|
||||
const info = msg.info as SessionMessageInfo
|
||||
if (!info || typeof info !== "object") continue
|
||||
if (info.role !== "user") continue
|
||||
|
||||
const id = (info as { id?: unknown }).id
|
||||
if (typeof id !== "string") continue
|
||||
|
||||
const parts = Array.isArray(msg.parts) ? (msg.parts as SessionMessagePart[]) : []
|
||||
const text = extractMessageText(parts)
|
||||
if (text.length === 0) continue
|
||||
|
||||
entries.push({ promptText: text, userMessageID: id })
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
function findLatestUserPrompt(payload: unknown): string {
|
||||
if (!Array.isArray(payload)) {
|
||||
return ""
|
||||
}
|
||||
|
||||
for (let index = payload.length - 1; index >= 0; index -= 1) {
|
||||
const candidate = payload[index] as SessionMessagePayload
|
||||
if (!candidate || typeof candidate !== "object") continue
|
||||
|
||||
const info = candidate.info as SessionMessageInfo
|
||||
if (!info || typeof info !== "object") continue
|
||||
if (info.role !== "user") continue
|
||||
|
||||
const parts = Array.isArray(candidate.parts) ? (candidate.parts as SessionMessagePart[]) : []
|
||||
const text = extractMessageText(parts)
|
||||
if (text.length > 0) {
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
function extractAssistantContext(info: SessionMessageInfo): LatestAssistantContext | null {
|
||||
const providerID = maybeString(info.providerID)
|
||||
const modelID = maybeString(info.modelID)
|
||||
const pathValue = info.path
|
||||
const pathRecord = pathValue && typeof pathValue === "object" ? (pathValue as { cwd?: unknown }) : null
|
||||
const workingDirectory = maybeString(pathRecord?.cwd)
|
||||
const agent = maybeString(info.agent)
|
||||
|
||||
if (!providerID && !modelID && !workingDirectory && !agent) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
providerID,
|
||||
modelID,
|
||||
workingDirectory,
|
||||
agent,
|
||||
}
|
||||
}
|
||||
|
||||
function findLatestAssistantCompletion(payload: unknown): LatestAssistantSnapshot {
|
||||
if (!Array.isArray(payload)) {
|
||||
return {
|
||||
text: "",
|
||||
context: null,
|
||||
}
|
||||
}
|
||||
|
||||
for (let index = payload.length - 1; index >= 0; index -= 1) {
|
||||
const candidate = payload[index] as SessionMessagePayload
|
||||
if (!candidate || typeof candidate !== "object") continue
|
||||
|
||||
const info = candidate.info as SessionMessageInfo
|
||||
if (!info || typeof info !== "object") continue
|
||||
if (info.role !== "assistant") continue
|
||||
|
||||
const time = info.time as { completed?: unknown } | undefined
|
||||
if (!time || typeof time !== "object") continue
|
||||
if (typeof time.completed !== "number") continue
|
||||
const context = extractAssistantContext(info)
|
||||
|
||||
const parts = Array.isArray(candidate.parts) ? (candidate.parts as SessionMessagePart[]) : []
|
||||
const text = extractMessageText(parts)
|
||||
|
||||
if (text.length > 0 || context) {
|
||||
return {
|
||||
text,
|
||||
context,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
text: "",
|
||||
context: null,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
|
||||
import {
|
||||
DEFAULT_RELAY_URL,
|
||||
parseSessionItems,
|
||||
persistServerState,
|
||||
restoreServerState,
|
||||
serverBases,
|
||||
looksLikeLocalHost,
|
||||
type ServerItem,
|
||||
} from "@/lib/server-sessions"
|
||||
|
||||
export { DEFAULT_RELAY_URL, looksLikeLocalHost, type ServerItem, type SessionItem } from "@/lib/server-sessions"
|
||||
|
||||
export function useServerSessions() {
|
||||
const [servers, setServers] = useState<ServerItem[]>([])
|
||||
const [activeServerId, setActiveServerId] = useState<string | null>(null)
|
||||
const [activeSessionId, setActiveSessionId] = useState<string | null>(null)
|
||||
|
||||
const serversRef = useRef<ServerItem[]>([])
|
||||
const restoredRef = useRef(false)
|
||||
const refreshSeqRef = useRef<Record<string, number>>({})
|
||||
const activeServerIdRef = useRef<string | null>(null)
|
||||
const activeSessionIdRef = useRef<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
serversRef.current = servers
|
||||
}, [servers])
|
||||
|
||||
useEffect(() => {
|
||||
activeServerIdRef.current = activeServerId
|
||||
}, [activeServerId])
|
||||
|
||||
useEffect(() => {
|
||||
activeSessionIdRef.current = activeSessionId
|
||||
}, [activeSessionId])
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
const next = await restoreServerState()
|
||||
if (!mounted || !next) return
|
||||
|
||||
setServers(next.servers)
|
||||
setActiveServerId(next.activeServerId)
|
||||
setActiveSessionId(next.activeSessionId)
|
||||
console.log("[Server] restore", {
|
||||
count: next.servers.length,
|
||||
activeServerId: next.activeServerId,
|
||||
})
|
||||
} finally {
|
||||
restoredRef.current = true
|
||||
}
|
||||
})()
|
||||
|
||||
return () => {
|
||||
mounted = false
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!restoredRef.current) return
|
||||
|
||||
void persistServerState(servers, activeServerId, activeSessionId).catch(() => {})
|
||||
}, [activeServerId, activeSessionId, servers])
|
||||
|
||||
const refreshServerStatusAndSessions = useCallback(async (serverID: string, includeSessions = true) => {
|
||||
const server = serversRef.current.find((item) => item.id === serverID)
|
||||
if (!server) return
|
||||
|
||||
const req = (refreshSeqRef.current[serverID] ?? 0) + 1
|
||||
refreshSeqRef.current[serverID] = req
|
||||
const current = () => refreshSeqRef.current[serverID] === req
|
||||
|
||||
const candidates = serverBases(server.url)
|
||||
const base = candidates[0] ?? server.url.replace(/\/+$/, "")
|
||||
const healthURL = `${base}/health`
|
||||
const sessionsURL = `${base}/experimental/session?limit=100`
|
||||
let insecureRemote = false
|
||||
try {
|
||||
const parsedBase = new URL(base)
|
||||
insecureRemote = parsedBase.protocol === "http:" && !looksLikeLocalHost(parsedBase.hostname)
|
||||
} catch {
|
||||
insecureRemote = base.startsWith("http://")
|
||||
}
|
||||
|
||||
console.log("[Server] refresh:start", {
|
||||
id: server.id,
|
||||
name: server.name,
|
||||
base,
|
||||
healthURL,
|
||||
sessionsURL,
|
||||
includeSessions,
|
||||
})
|
||||
|
||||
setServers((prev) =>
|
||||
prev.map((item) => (item.id === serverID && includeSessions ? { ...item, sessionsLoading: true } : item)),
|
||||
)
|
||||
|
||||
let activeBase = base
|
||||
try {
|
||||
let healthRes: Response | null = null
|
||||
let healthErr: unknown
|
||||
|
||||
for (const item of candidates) {
|
||||
const url = `${item}/health`
|
||||
try {
|
||||
const next = await fetch(url)
|
||||
if (next.ok) {
|
||||
healthRes = next
|
||||
activeBase = item
|
||||
if (item !== server.url.replace(/\/+$/, "") && current()) {
|
||||
setServers((prev) => prev.map((entry) => (entry.id === serverID ? { ...entry, url: item } : entry)))
|
||||
console.log("[Server] refresh:scheme-upgrade", {
|
||||
id: server.id,
|
||||
from: server.url,
|
||||
to: item,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
healthRes = next
|
||||
activeBase = item
|
||||
} catch (err) {
|
||||
healthErr = err
|
||||
console.log("[Server] health:attempt-error", {
|
||||
id: server.id,
|
||||
url,
|
||||
error: err instanceof Error ? `${err.name}: ${err.message}` : String(err),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const online = !!healthRes?.ok
|
||||
if (!current()) {
|
||||
console.log("[Server] refresh:stale-skip", { id: server.id, req })
|
||||
return
|
||||
}
|
||||
|
||||
console.log("[Server] health", {
|
||||
id: server.id,
|
||||
base: activeBase,
|
||||
url: `${activeBase}/health`,
|
||||
status: healthRes?.status ?? "fetch_error",
|
||||
online,
|
||||
})
|
||||
|
||||
if (!online) {
|
||||
setServers((prev) =>
|
||||
prev.map((item) =>
|
||||
item.id === serverID ? { ...item, status: "offline", sessionsLoading: false, sessions: [] } : item,
|
||||
),
|
||||
)
|
||||
console.log("[Server] refresh:offline", {
|
||||
id: server.id,
|
||||
base,
|
||||
candidates,
|
||||
error: healthErr instanceof Error ? `${healthErr.name}: ${healthErr.message}` : String(healthErr),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!includeSessions) {
|
||||
setServers((prev) =>
|
||||
prev.map((item) => (item.id === serverID ? { ...item, status: "online", sessionsLoading: false } : item)),
|
||||
)
|
||||
console.log("[Server] refresh:online", { id: server.id, base })
|
||||
return
|
||||
}
|
||||
|
||||
const resolvedSessionsURL = `${activeBase}/experimental/session?limit=100&roots=true`
|
||||
const sessionsRes = await fetch(resolvedSessionsURL)
|
||||
if (!current()) {
|
||||
console.log("[Server] refresh:stale-skip", { id: server.id, req })
|
||||
return
|
||||
}
|
||||
|
||||
if (!sessionsRes.ok) {
|
||||
console.log("[Server] sessions:http-error", {
|
||||
id: server.id,
|
||||
url: resolvedSessionsURL,
|
||||
status: sessionsRes.status,
|
||||
})
|
||||
}
|
||||
|
||||
const json = sessionsRes.ok ? await sessionsRes.json() : []
|
||||
const sessions = parseSessionItems(json)
|
||||
|
||||
setServers((prev) =>
|
||||
prev.map((item) =>
|
||||
item.id === serverID ? { ...item, status: "online", sessionsLoading: false, sessions } : item,
|
||||
),
|
||||
)
|
||||
console.log("[Server] sessions", { id: server.id, count: sessions.length })
|
||||
} catch (err) {
|
||||
if (!current()) {
|
||||
console.log("[Server] refresh:stale-skip", { id: server.id, req })
|
||||
return
|
||||
}
|
||||
|
||||
setServers((prev) =>
|
||||
prev.map((item) =>
|
||||
item.id === serverID ? { ...item, status: "offline", sessionsLoading: false, sessions: [] } : item,
|
||||
),
|
||||
)
|
||||
console.log("[Server] refresh:error", {
|
||||
id: server.id,
|
||||
base,
|
||||
healthURL,
|
||||
sessionsURL,
|
||||
candidates,
|
||||
insecureRemote,
|
||||
error: err instanceof Error ? `${err.name}: ${err.message}` : String(err),
|
||||
})
|
||||
if (insecureRemote) {
|
||||
console.log("[Server] refresh:hint", {
|
||||
id: server.id,
|
||||
message: "Remote http:// host may be blocked by iOS ATS; prefer https:// for non-local hosts.",
|
||||
})
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const refreshAllServerHealth = useCallback(() => {
|
||||
const ids = serversRef.current.map((item) => item.id)
|
||||
ids.forEach((id) => {
|
||||
void refreshServerStatusAndSessions(id, false)
|
||||
})
|
||||
}, [refreshServerStatusAndSessions])
|
||||
|
||||
const selectServer = useCallback((id: string) => {
|
||||
setActiveServerId(id)
|
||||
setActiveSessionId(null)
|
||||
}, [])
|
||||
|
||||
const selectSession = useCallback((id: string) => {
|
||||
setActiveSessionId(id)
|
||||
}, [])
|
||||
|
||||
const removeServer = useCallback((id: string) => {
|
||||
setServers((prev) => prev.filter((item) => item.id !== id))
|
||||
setActiveServerId((prev) => (prev === id ? null : prev))
|
||||
if (activeServerIdRef.current === id) {
|
||||
setActiveSessionId(null)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const addServer = useCallback(
|
||||
(serverURL: string, relayURL: string, relaySecretRaw: string, serverIDRaw?: string) => {
|
||||
const raw = serverURL.trim()
|
||||
if (!raw) return false
|
||||
|
||||
const normalized = raw.startsWith("http://") || raw.startsWith("https://") ? raw : `http://${raw}`
|
||||
|
||||
const rawRelay = relayURL.trim()
|
||||
const relayNormalizedRaw = rawRelay.length > 0 ? rawRelay : DEFAULT_RELAY_URL
|
||||
const normalizedRelay =
|
||||
relayNormalizedRaw.startsWith("http://") || relayNormalizedRaw.startsWith("https://")
|
||||
? relayNormalizedRaw
|
||||
: `http://${relayNormalizedRaw}`
|
||||
|
||||
let parsed: URL
|
||||
let relayParsed: URL
|
||||
try {
|
||||
parsed = new URL(normalized)
|
||||
relayParsed = new URL(normalizedRelay)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
|
||||
const id = `srv-${Date.now()}`
|
||||
const relaySecret = relaySecretRaw.trim()
|
||||
const serverID = typeof serverIDRaw === "string" && serverIDRaw.length > 0 ? serverIDRaw : null
|
||||
const url = `${parsed.protocol}//${parsed.host}`
|
||||
const inferredName =
|
||||
parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost" ? "Local OpenCode" : parsed.hostname
|
||||
const relay = `${relayParsed.protocol}//${relayParsed.host}`
|
||||
const existing = serversRef.current.find(
|
||||
(item) =>
|
||||
item.url === url &&
|
||||
item.relayURL === relay &&
|
||||
item.relaySecret.trim() === relaySecret &&
|
||||
(!serverID || item.serverID === serverID || item.serverID === null),
|
||||
)
|
||||
|
||||
if (existing) {
|
||||
if (serverID && existing.serverID !== serverID) {
|
||||
setServers((prev) =>
|
||||
prev.map((item) => (item.id === existing.id ? { ...item, serverID: serverID ?? item.serverID } : item)),
|
||||
)
|
||||
}
|
||||
|
||||
setActiveServerId(existing.id)
|
||||
setActiveSessionId(null)
|
||||
void refreshServerStatusAndSessions(existing.id)
|
||||
return true
|
||||
}
|
||||
|
||||
setServers((prev) => [
|
||||
...prev,
|
||||
{
|
||||
id,
|
||||
name: inferredName,
|
||||
url,
|
||||
serverID,
|
||||
relayURL: relay,
|
||||
relaySecret,
|
||||
status: "offline",
|
||||
sessions: [],
|
||||
sessionsLoading: false,
|
||||
},
|
||||
])
|
||||
setActiveServerId(id)
|
||||
setActiveSessionId(null)
|
||||
void refreshServerStatusAndSessions(id)
|
||||
return true
|
||||
},
|
||||
[refreshServerStatusAndSessions],
|
||||
)
|
||||
|
||||
const createSession = useCallback(
|
||||
async (
|
||||
serverID: string,
|
||||
options?: {
|
||||
directory?: string
|
||||
workspaceID?: string
|
||||
title?: string
|
||||
},
|
||||
) => {
|
||||
const server = serversRef.current.find((item) => item.id === serverID)
|
||||
if (!server) {
|
||||
return null
|
||||
}
|
||||
|
||||
const base = server.url.replace(/\/+$/, "")
|
||||
const params = new URLSearchParams()
|
||||
const directory = options?.directory?.trim()
|
||||
const workspaceID = options?.workspaceID?.trim()
|
||||
const title = options?.title?.trim()
|
||||
|
||||
if (directory) {
|
||||
params.set("directory", directory)
|
||||
}
|
||||
|
||||
const body: {
|
||||
workspaceID?: string
|
||||
title?: string
|
||||
} = {}
|
||||
|
||||
if (workspaceID) {
|
||||
body.workspaceID = workspaceID
|
||||
}
|
||||
|
||||
if (title) {
|
||||
body.title = title
|
||||
}
|
||||
|
||||
const query = params.toString()
|
||||
const endpoint = `${base}/session${query ? `?${query}` : ""}`
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
console.log("[Server] session:create:http-error", {
|
||||
id: server.id,
|
||||
endpoint,
|
||||
status: response.status,
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as unknown
|
||||
const parsed = parseSessionItems([payload])[0]
|
||||
|
||||
if (!parsed) {
|
||||
void refreshServerStatusAndSessions(serverID)
|
||||
return null
|
||||
}
|
||||
|
||||
const created = parsed.updated > 0 ? parsed : { ...parsed, updated: Date.now() }
|
||||
|
||||
setServers((prev) =>
|
||||
prev.map((item) => {
|
||||
if (item.id !== serverID) return item
|
||||
|
||||
const sessions = [created, ...item.sessions.filter((session) => session.id !== created.id)].sort(
|
||||
(a, b) => b.updated - a.updated,
|
||||
)
|
||||
|
||||
return {
|
||||
...item,
|
||||
status: "online",
|
||||
sessionsLoading: false,
|
||||
sessions,
|
||||
}
|
||||
}),
|
||||
)
|
||||
setActiveServerId(serverID)
|
||||
setActiveSessionId(created.id)
|
||||
|
||||
console.log("[Server] session:create", {
|
||||
id: server.id,
|
||||
sessionID: created.id,
|
||||
hasDirectory: Boolean(created.directory),
|
||||
hasWorkspaceID: Boolean(created.workspaceID),
|
||||
})
|
||||
|
||||
return created
|
||||
} catch (err) {
|
||||
console.log("[Server] session:create:error", {
|
||||
id: server.id,
|
||||
endpoint,
|
||||
error: err instanceof Error ? `${err.name}: ${err.message}` : String(err),
|
||||
})
|
||||
return null
|
||||
}
|
||||
},
|
||||
[refreshServerStatusAndSessions],
|
||||
)
|
||||
|
||||
const findServerForSession = useCallback(
|
||||
async (sessionID: string, preferredServerID?: string | null): Promise<ServerItem | null> => {
|
||||
if (!serversRef.current.length && !restoredRef.current) {
|
||||
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 150))
|
||||
if (serversRef.current.length > 0 || restoredRef.current) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (preferredServerID) {
|
||||
const preferred = serversRef.current.find((server) => server.serverID === preferredServerID)
|
||||
if (preferred?.sessions.some((session) => session.id === sessionID)) {
|
||||
return preferred
|
||||
}
|
||||
if (preferred) {
|
||||
await refreshServerStatusAndSessions(preferred.id)
|
||||
const refreshed = serversRef.current.find((server) => server.id === preferred.id)
|
||||
if (refreshed?.sessions.some((session) => session.id === sessionID)) {
|
||||
return refreshed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const direct = serversRef.current.find((server) => server.sessions.some((session) => session.id === sessionID))
|
||||
if (direct) return direct
|
||||
|
||||
const ids = serversRef.current.map((server) => server.id)
|
||||
for (const id of ids) {
|
||||
await refreshServerStatusAndSessions(id)
|
||||
const matched = serversRef.current.find(
|
||||
(server) => server.id === id && server.sessions.some((session) => session.id === sessionID),
|
||||
)
|
||||
if (matched) {
|
||||
return matched
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
[refreshServerStatusAndSessions],
|
||||
)
|
||||
|
||||
return {
|
||||
servers,
|
||||
setServers,
|
||||
serversRef,
|
||||
activeServerId,
|
||||
setActiveServerId,
|
||||
activeServerIdRef,
|
||||
activeSessionId,
|
||||
setActiveSessionId,
|
||||
activeSessionIdRef,
|
||||
restoredRef,
|
||||
refreshServerStatusAndSessions,
|
||||
refreshAllServerHealth,
|
||||
selectServer,
|
||||
selectSession,
|
||||
removeServer,
|
||||
addServer,
|
||||
createSession,
|
||||
findServerForSession,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Learn more about light and dark modes:
|
||||
* https://docs.expo.dev/guides/color-schemes/
|
||||
*/
|
||||
|
||||
import { Colors } from '@/constants/theme';
|
||||
import { useColorScheme } from '@/hooks/use-color-scheme';
|
||||
|
||||
export function useTheme() {
|
||||
const scheme = useColorScheme();
|
||||
const theme = scheme === 'unspecified' ? 'light' : scheme;
|
||||
|
||||
return Colors[theme];
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
export type OpenCodeEvent = {
|
||||
type: string
|
||||
properties?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type MonitorEventType = "complete" | "permission" | "error"
|
||||
|
||||
export function extractSessionID(event: OpenCodeEvent): string | null {
|
||||
const props = event.properties ?? {}
|
||||
|
||||
const fromDirect = props.sessionID
|
||||
if (typeof fromDirect === "string" && fromDirect.length > 0) return fromDirect
|
||||
|
||||
const info = props.info
|
||||
if (info && typeof info === "object") {
|
||||
const infoSessionID = (info as Record<string, unknown>).sessionID
|
||||
if (typeof infoSessionID === "string" && infoSessionID.length > 0) return infoSessionID
|
||||
}
|
||||
|
||||
const part = props.part
|
||||
if (part && typeof part === "object") {
|
||||
const partSessionID = (part as Record<string, unknown>).sessionID
|
||||
if (typeof partSessionID === "string" && partSessionID.length > 0) return partSessionID
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function classifyMonitorEvent(event: OpenCodeEvent): MonitorEventType | null {
|
||||
const type = event.type
|
||||
const lowerType = type.toLowerCase()
|
||||
|
||||
if (lowerType === "permission.asked" || lowerType === "permission") {
|
||||
return "permission"
|
||||
}
|
||||
|
||||
if (lowerType.includes("error")) {
|
||||
return "error"
|
||||
}
|
||||
|
||||
if (type === "session.status") {
|
||||
const status = event.properties?.status
|
||||
if (status && typeof status === "object") {
|
||||
const statusType = (status as Record<string, unknown>).type
|
||||
if (statusType === "idle") {
|
||||
return "complete"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function formatMonitorEventLabel(eventType: MonitorEventType): string {
|
||||
switch (eventType) {
|
||||
case "complete":
|
||||
return "Session complete"
|
||||
case "permission":
|
||||
return "Action needed"
|
||||
case "error":
|
||||
return "Session error"
|
||||
default:
|
||||
return "Session update"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
export type PendingPermissionRequest = {
|
||||
id: string
|
||||
sessionID: string
|
||||
permission: string
|
||||
patterns: string[]
|
||||
metadata: Record<string, unknown>
|
||||
always: string[]
|
||||
tool?: {
|
||||
messageID: string
|
||||
callID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type PermissionCardSection = {
|
||||
label: string
|
||||
text: string
|
||||
mono?: boolean
|
||||
}
|
||||
|
||||
export type PermissionCardModel = {
|
||||
eyebrow: string
|
||||
title: string
|
||||
body: string
|
||||
sections: PermissionCardSection[]
|
||||
}
|
||||
|
||||
function record(input: unknown): Record<string, unknown> | null {
|
||||
if (!input || typeof input !== "object") return null
|
||||
return input as Record<string, unknown>
|
||||
}
|
||||
|
||||
function maybeString(input: unknown): string | undefined {
|
||||
return typeof input === "string" && input.trim().length > 0 ? input : undefined
|
||||
}
|
||||
|
||||
function stringList(input: unknown): string[] {
|
||||
if (!Array.isArray(input)) return []
|
||||
return input.filter((item): item is string => typeof item === "string" && item.length > 0)
|
||||
}
|
||||
|
||||
function previewText(input: string, options?: { maxLines?: number; maxChars?: number }): string {
|
||||
const maxLines = options?.maxLines ?? 18
|
||||
const maxChars = options?.maxChars ?? 1200
|
||||
const normalized = input.replace(/\r\n/g, "\n").trim()
|
||||
if (!normalized) return ""
|
||||
|
||||
const lines = normalized.split("\n")
|
||||
const sliced = lines.slice(0, maxLines)
|
||||
let text = sliced.join("\n")
|
||||
|
||||
if (text.length > maxChars) {
|
||||
text = `${text.slice(0, maxChars).trimEnd()}\n…`
|
||||
} else if (lines.length > maxLines) {
|
||||
text = `${text}\n…`
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
function formatPath(input: string): string {
|
||||
return input.replace(/\\/g, "/")
|
||||
}
|
||||
|
||||
function permissionTool(input: unknown): PendingPermissionRequest["tool"] | undefined {
|
||||
const value = record(input)
|
||||
if (!value) return
|
||||
|
||||
const messageID = maybeString(value.messageID)
|
||||
const callID = maybeString(value.callID)
|
||||
if (!messageID || !callID) return
|
||||
|
||||
return {
|
||||
messageID,
|
||||
callID,
|
||||
}
|
||||
}
|
||||
|
||||
function parsePendingPermissionRequest(input: unknown): PendingPermissionRequest | null {
|
||||
const value = record(input)
|
||||
if (!value) return null
|
||||
|
||||
const id = maybeString(value.id)
|
||||
const sessionID = maybeString(value.sessionID)
|
||||
const permission = maybeString(value.permission)
|
||||
if (!id || !sessionID || !permission) return null
|
||||
|
||||
return {
|
||||
id,
|
||||
sessionID,
|
||||
permission,
|
||||
patterns: stringList(value.patterns),
|
||||
metadata: record(value.metadata) ?? {},
|
||||
always: stringList(value.always),
|
||||
tool: permissionTool(value.tool),
|
||||
}
|
||||
}
|
||||
|
||||
export { parsePendingPermissionRequest }
|
||||
|
||||
export function parsePendingPermissionRequests(payload: unknown): PendingPermissionRequest[] {
|
||||
if (!Array.isArray(payload)) return []
|
||||
|
||||
return payload
|
||||
.map((item) => parsePendingPermissionRequest(item))
|
||||
.filter((item): item is PendingPermissionRequest => item !== null)
|
||||
}
|
||||
|
||||
function firstPattern(request: PendingPermissionRequest): string | undefined {
|
||||
return request.patterns.find((item) => item.trim().length > 0)
|
||||
}
|
||||
|
||||
function externalDirectory(request: PendingPermissionRequest): string | undefined {
|
||||
const fromMetadata = maybeString(request.metadata.parentDir) ?? maybeString(request.metadata.filepath)
|
||||
if (fromMetadata) return fromMetadata
|
||||
|
||||
const pattern = firstPattern(request)
|
||||
if (!pattern) return
|
||||
return pattern.endsWith("/*") ? pattern.slice(0, -2) : pattern
|
||||
}
|
||||
|
||||
function allowScopeSection(request: PendingPermissionRequest): PermissionCardSection | null {
|
||||
if (request.always.length === 0) return null
|
||||
if (
|
||||
request.always.length === request.patterns.length &&
|
||||
request.always.every((item, index) => item === request.patterns[index])
|
||||
) {
|
||||
return null
|
||||
}
|
||||
if (request.always.length === 1 && request.always[0] === "*") {
|
||||
return {
|
||||
label: "Always allow",
|
||||
text: "Applies to all future requests of this permission until OpenCode restarts.",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
label: "Always allow scope",
|
||||
text: previewText(request.always.join("\n"), { maxLines: 8, maxChars: 600 }),
|
||||
mono: true,
|
||||
}
|
||||
}
|
||||
|
||||
export function buildPermissionCardModel(request: PendingPermissionRequest): PermissionCardModel {
|
||||
const filepath = maybeString(request.metadata.filepath)
|
||||
const diff = maybeString(request.metadata.diff)
|
||||
const commandText = previewText(request.patterns.join("\n"), { maxLines: 6, maxChars: 700 })
|
||||
const scope = allowScopeSection(request)
|
||||
|
||||
if (request.permission === "edit") {
|
||||
const sections: PermissionCardSection[] = []
|
||||
if (filepath) {
|
||||
sections.push({ label: "File", text: formatPath(filepath), mono: true })
|
||||
}
|
||||
if (diff) {
|
||||
sections.push({ label: "Diff preview", text: previewText(diff), mono: true })
|
||||
}
|
||||
if (scope) sections.push(scope)
|
||||
|
||||
return {
|
||||
eyebrow: "EDIT",
|
||||
title: "Allow file edit?",
|
||||
body: "OpenCode wants to change a file in this session.",
|
||||
sections,
|
||||
}
|
||||
}
|
||||
|
||||
if (request.permission === "bash") {
|
||||
const sections: PermissionCardSection[] = []
|
||||
if (commandText) {
|
||||
sections.push({ label: "Command", text: commandText, mono: true })
|
||||
}
|
||||
if (scope) sections.push(scope)
|
||||
|
||||
return {
|
||||
eyebrow: "BASH",
|
||||
title: "Allow shell command?",
|
||||
body: "OpenCode wants to run a shell command for this session.",
|
||||
sections,
|
||||
}
|
||||
}
|
||||
|
||||
if (request.permission === "read") {
|
||||
const sections: PermissionCardSection[] = []
|
||||
const path = firstPattern(request)
|
||||
if (path) {
|
||||
sections.push({ label: "Path", text: formatPath(path), mono: true })
|
||||
}
|
||||
if (scope) sections.push(scope)
|
||||
|
||||
return {
|
||||
eyebrow: "READ",
|
||||
title: "Allow file read?",
|
||||
body: "OpenCode wants to read a path from your machine.",
|
||||
sections,
|
||||
}
|
||||
}
|
||||
|
||||
if (request.permission === "external_directory") {
|
||||
const sections: PermissionCardSection[] = []
|
||||
const dir = externalDirectory(request)
|
||||
if (dir) {
|
||||
sections.push({ label: "Directory", text: formatPath(dir), mono: true })
|
||||
}
|
||||
if (request.patterns.length > 0) {
|
||||
sections.push({
|
||||
label: "Patterns",
|
||||
text: previewText(request.patterns.join("\n"), { maxLines: 8, maxChars: 600 }),
|
||||
mono: true,
|
||||
})
|
||||
}
|
||||
if (scope) sections.push(scope)
|
||||
|
||||
return {
|
||||
eyebrow: "DIRECTORY",
|
||||
title: "Allow external access?",
|
||||
body: "OpenCode wants to work with files outside the current project directory.",
|
||||
sections,
|
||||
}
|
||||
}
|
||||
|
||||
if (request.permission === "task") {
|
||||
const sections: PermissionCardSection[] = []
|
||||
if (request.patterns.length > 0) {
|
||||
sections.push({
|
||||
label: "Patterns",
|
||||
text: previewText(request.patterns.join("\n"), { maxLines: 8, maxChars: 600 }),
|
||||
mono: true,
|
||||
})
|
||||
}
|
||||
if (scope) sections.push(scope)
|
||||
|
||||
return {
|
||||
eyebrow: "TASK",
|
||||
title: "Allow delegated task?",
|
||||
body: "OpenCode wants to launch another task as part of this session.",
|
||||
sections,
|
||||
}
|
||||
}
|
||||
|
||||
const sections: PermissionCardSection[] = []
|
||||
if (request.patterns.length > 0) {
|
||||
sections.push({
|
||||
label: "Patterns",
|
||||
text: previewText(request.patterns.join("\n"), { maxLines: 8, maxChars: 600 }),
|
||||
mono: true,
|
||||
})
|
||||
}
|
||||
if (scope) sections.push(scope)
|
||||
|
||||
return {
|
||||
eyebrow: request.permission.toUpperCase(),
|
||||
title: `Allow ${request.permission}?`,
|
||||
body: "OpenCode needs your permission before it can continue this session.",
|
||||
sections,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
export type RegisterDeviceInput = {
|
||||
relayBaseURL: string
|
||||
secret: string
|
||||
deviceToken: string
|
||||
bundleId?: string
|
||||
apnsEnv?: "sandbox" | "production"
|
||||
}
|
||||
|
||||
export type UnregisterDeviceInput = {
|
||||
relayBaseURL: string
|
||||
secret: string
|
||||
deviceToken: string
|
||||
}
|
||||
|
||||
function normalizeBase(url: string): string {
|
||||
return url.replace(/\/+$/, "")
|
||||
}
|
||||
|
||||
async function postRelay(path: string, relayBaseURL: string, body: Record<string, unknown>): Promise<void> {
|
||||
const relay = normalizeBase(relayBaseURL)
|
||||
const response = await fetch(`${relay}${path}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text()
|
||||
throw new Error(`Relay request failed (${response.status}): ${text || response.statusText}`)
|
||||
}
|
||||
}
|
||||
|
||||
export async function registerRelayDevice(input: RegisterDeviceInput): Promise<void> {
|
||||
await postRelay("/v1/device/register", input.relayBaseURL, {
|
||||
secret: input.secret,
|
||||
deviceToken: input.deviceToken,
|
||||
bundleId: input.bundleId,
|
||||
apnsEnv: input.apnsEnv,
|
||||
})
|
||||
}
|
||||
|
||||
export async function unregisterRelayDevice(input: UnregisterDeviceInput): Promise<void> {
|
||||
await postRelay("/v1/device/unregister", input.relayBaseURL, {
|
||||
secret: input.secret,
|
||||
deviceToken: input.deviceToken,
|
||||
})
|
||||
}
|
||||
|
||||
export async function sendRelayTestEvent(input: {
|
||||
relayBaseURL: string
|
||||
secret: string
|
||||
sessionID: string
|
||||
}): Promise<void> {
|
||||
await postRelay("/v1/event", input.relayBaseURL, {
|
||||
secret: input.secret,
|
||||
eventType: "permission",
|
||||
sessionID: input.sessionID,
|
||||
title: "APN relay test",
|
||||
body: "If you can read this, APN relay registration is working.",
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
import * as FileSystem from "expo-file-system/legacy"
|
||||
|
||||
export const DEFAULT_RELAY_URL = "https://apn.dev.opencode.ai"
|
||||
|
||||
const SERVER_STATE_FILE = `${FileSystem.documentDirectory}mobile-voice-servers.json`
|
||||
|
||||
export type SessionItem = {
|
||||
id: string
|
||||
title: string
|
||||
updated: number
|
||||
directory?: string
|
||||
workspaceID?: string
|
||||
projectID?: string
|
||||
}
|
||||
|
||||
type ServerSessionPayload = {
|
||||
id?: unknown
|
||||
title?: unknown
|
||||
directory?: unknown
|
||||
workspaceID?: unknown
|
||||
projectID?: unknown
|
||||
time?: {
|
||||
updated?: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type ServerItem = {
|
||||
id: string
|
||||
name: string
|
||||
url: string
|
||||
serverID: string | null
|
||||
relayURL: string
|
||||
relaySecret: string
|
||||
status: "checking" | "online" | "offline"
|
||||
sessions: SessionItem[]
|
||||
sessionsLoading: boolean
|
||||
}
|
||||
|
||||
type SavedServer = {
|
||||
id: string
|
||||
name: string
|
||||
url: string
|
||||
serverID: string | null
|
||||
relayURL: string
|
||||
relaySecret: string
|
||||
}
|
||||
|
||||
type SavedState = {
|
||||
servers: SavedServer[]
|
||||
activeServerId: string | null
|
||||
activeSessionId: string | null
|
||||
}
|
||||
|
||||
export function parseSessionItems(payload: unknown): SessionItem[] {
|
||||
if (!Array.isArray(payload)) return []
|
||||
|
||||
return payload
|
||||
.filter((item): item is ServerSessionPayload => !!item && typeof item === "object")
|
||||
.map((item) => {
|
||||
const directory = typeof item.directory === "string" && item.directory.length > 0 ? item.directory : undefined
|
||||
const workspaceID =
|
||||
typeof item.workspaceID === "string" && item.workspaceID.length > 0 ? item.workspaceID : undefined
|
||||
const projectID = typeof item.projectID === "string" && item.projectID.length > 0 ? item.projectID : undefined
|
||||
|
||||
return {
|
||||
id: String(item.id ?? ""),
|
||||
title: String(item.title ?? item.id ?? "Untitled session"),
|
||||
updated: Number(item.time?.updated ?? 0),
|
||||
directory,
|
||||
workspaceID,
|
||||
projectID,
|
||||
}
|
||||
})
|
||||
.filter((item) => item.id.length > 0)
|
||||
.sort((a, b) => b.updated - a.updated)
|
||||
}
|
||||
|
||||
function isCarrierGradeNat(hostname: string): boolean {
|
||||
const match = /^100\.(\d{1,3})\./.exec(hostname)
|
||||
if (!match) return false
|
||||
const octet = Number(match[1])
|
||||
return octet >= 64 && octet <= 127
|
||||
}
|
||||
|
||||
export function looksLikeLocalHost(hostname: string): boolean {
|
||||
return (
|
||||
hostname === "127.0.0.1" ||
|
||||
hostname === "::1" ||
|
||||
hostname === "localhost" ||
|
||||
hostname.endsWith(".local") ||
|
||||
hostname.startsWith("10.") ||
|
||||
hostname.startsWith("192.168.") ||
|
||||
/^172\.(1[6-9]|2\d|3[0-1])\./.test(hostname) ||
|
||||
isCarrierGradeNat(hostname)
|
||||
)
|
||||
}
|
||||
|
||||
export function serverBases(input: string): string[] {
|
||||
const base = input.replace(/\/+$/, "")
|
||||
const list = [base]
|
||||
try {
|
||||
const url = new URL(base)
|
||||
const local = looksLikeLocalHost(url.hostname)
|
||||
const tailnet = url.hostname.endsWith(".ts.net")
|
||||
const secure = `https://${url.host}`
|
||||
const insecure = `http://${url.host}`
|
||||
if (url.protocol === "http:" && !local) {
|
||||
list.push(secure)
|
||||
} else if (url.protocol === "https:" && tailnet) {
|
||||
list.push(insecure)
|
||||
}
|
||||
} catch {
|
||||
// Keep original base only.
|
||||
}
|
||||
return [...new Set(list)]
|
||||
}
|
||||
|
||||
function toSaved(servers: ServerItem[], activeServerId: string | null, activeSessionId: string | null): SavedState {
|
||||
return {
|
||||
servers: servers.map((item) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
url: item.url,
|
||||
serverID: item.serverID,
|
||||
relayURL: item.relayURL,
|
||||
relaySecret: item.relaySecret,
|
||||
})),
|
||||
activeServerId,
|
||||
activeSessionId,
|
||||
}
|
||||
}
|
||||
|
||||
function fromSaved(input: SavedState): {
|
||||
servers: ServerItem[]
|
||||
activeServerId: string | null
|
||||
activeSessionId: string | null
|
||||
} {
|
||||
const servers = input.servers.map((item) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
url: item.url,
|
||||
serverID: item.serverID ?? null,
|
||||
relayURL: item.relayURL,
|
||||
relaySecret: item.relaySecret,
|
||||
status: "checking" as const,
|
||||
sessions: [] as SessionItem[],
|
||||
sessionsLoading: false,
|
||||
}))
|
||||
const hasActive = input.activeServerId && servers.some((item) => item.id === input.activeServerId)
|
||||
const activeServerId = hasActive ? input.activeServerId : (servers[0]?.id ?? null)
|
||||
return {
|
||||
servers,
|
||||
activeServerId,
|
||||
activeSessionId: hasActive ? input.activeSessionId : null,
|
||||
}
|
||||
}
|
||||
|
||||
export async function restoreServerState(): Promise<{
|
||||
servers: ServerItem[]
|
||||
activeServerId: string | null
|
||||
activeSessionId: string | null
|
||||
} | null> {
|
||||
try {
|
||||
const data = await FileSystem.readAsStringAsync(SERVER_STATE_FILE)
|
||||
if (!data) {
|
||||
return null
|
||||
}
|
||||
return fromSaved(JSON.parse(data) as SavedState)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function persistServerState(
|
||||
servers: ServerItem[],
|
||||
activeServerId: string | null,
|
||||
activeSessionId: string | null,
|
||||
): Promise<void> {
|
||||
const payload = toSaved(servers, activeServerId, activeSessionId)
|
||||
return FileSystem.writeAsStringAsync(SERVER_STATE_FILE, JSON.stringify(payload))
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
export type SSEMessage = {
|
||||
event?: string;
|
||||
data: string;
|
||||
id?: string;
|
||||
};
|
||||
|
||||
function parseBlock(block: string): SSEMessage | null {
|
||||
if (!block.trim()) return null;
|
||||
|
||||
const lines = block.split(/\r?\n/);
|
||||
let event: string | undefined;
|
||||
let id: string | undefined;
|
||||
const dataLines: string[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line || line.startsWith(':')) continue;
|
||||
|
||||
const sep = line.indexOf(':');
|
||||
const field = sep === -1 ? line : line.slice(0, sep);
|
||||
const value = sep === -1 ? '' : line.slice(sep + 1).replace(/^\s/, '');
|
||||
|
||||
if (field === 'event') {
|
||||
event = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field === 'id') {
|
||||
id = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field === 'data') {
|
||||
dataLines.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (dataLines.length === 0) return null;
|
||||
|
||||
return {
|
||||
event,
|
||||
id,
|
||||
data: dataLines.join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
export async function* parseSSEStream(stream: ReadableStream<Uint8Array>): AsyncGenerator<SSEMessage> {
|
||||
const reader = stream.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let pending = '';
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const result = await reader.read();
|
||||
if (result.done) break;
|
||||
|
||||
pending += decoder.decode(result.value, { stream: true });
|
||||
const blocks = pending.split(/\r?\n\r?\n/);
|
||||
pending = blocks.pop() ?? '';
|
||||
|
||||
for (const block of blocks) {
|
||||
const parsed = parseBlock(block);
|
||||
if (parsed) yield parsed;
|
||||
}
|
||||
}
|
||||
|
||||
pending += decoder.decode();
|
||||
const tail = parseBlock(pending);
|
||||
if (tail) yield tail;
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import * as Notifications from "expo-notifications"
|
||||
import * as TaskManager from "expo-task-manager"
|
||||
import { Platform } from "react-native"
|
||||
|
||||
const BACKGROUND_TASK_NAME = "monitoring-background-notification-task"
|
||||
|
||||
type BackgroundPayload = {
|
||||
eventType?: string
|
||||
sessionID?: string
|
||||
title?: string
|
||||
body?: string
|
||||
}
|
||||
|
||||
let configured = false
|
||||
|
||||
TaskManager.defineTask(BACKGROUND_TASK_NAME, async ({ data }: { data?: unknown }) => {
|
||||
const payload = data as BackgroundPayload | undefined
|
||||
const title = payload?.title ?? "OpenCode update"
|
||||
const body = payload?.body ?? "Your monitored session has a new update."
|
||||
|
||||
await Notifications.scheduleNotificationAsync({
|
||||
content: {
|
||||
title,
|
||||
body,
|
||||
data: payload ?? {},
|
||||
sound: "alert.wav",
|
||||
...(Platform.OS === "android" ? { channelId: "monitoring" } : {}),
|
||||
},
|
||||
trigger: null,
|
||||
})
|
||||
|
||||
return Notifications.BackgroundNotificationTaskResult.NewData
|
||||
})
|
||||
|
||||
export function configureNotificationBehavior(): void {
|
||||
if (configured) return
|
||||
configured = true
|
||||
|
||||
Notifications.setNotificationHandler({
|
||||
handleNotification: async () => ({
|
||||
shouldShowBanner: false,
|
||||
shouldShowList: false,
|
||||
shouldPlaySound: false,
|
||||
shouldSetBadge: false,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
export async function registerBackgroundNotificationTask(): Promise<void> {
|
||||
const already = await TaskManager.isTaskRegisteredAsync(BACKGROUND_TASK_NAME)
|
||||
if (already) return
|
||||
await Notifications.registerTaskAsync(BACKGROUND_TASK_NAME)
|
||||
}
|
||||
|
||||
export async function ensureNotificationPermissions(): Promise<boolean> {
|
||||
if (Platform.OS === "android") {
|
||||
await Notifications.setNotificationChannelAsync("monitoring", {
|
||||
name: "OpenCode Monitoring",
|
||||
importance: Notifications.AndroidImportance.HIGH,
|
||||
sound: "alert.wav",
|
||||
})
|
||||
}
|
||||
|
||||
const existing = await Notifications.getPermissionsAsync()
|
||||
let granted = existing.granted
|
||||
|
||||
if (!granted) {
|
||||
const requested = await Notifications.requestPermissionsAsync()
|
||||
granted = requested.granted
|
||||
}
|
||||
|
||||
return granted
|
||||
}
|
||||
|
||||
export async function getDevicePushToken(): Promise<string | null> {
|
||||
const result = await Notifications.getDevicePushTokenAsync()
|
||||
if (typeof result.data !== "string" || result.data.length === 0) {
|
||||
return null
|
||||
}
|
||||
return result.data
|
||||
}
|
||||
|
||||
export function onPushTokenChange(callback: (token: string) => void): { remove: () => void } {
|
||||
return Notifications.addPushTokenListener((next: { data: unknown }) => {
|
||||
if (typeof next.data !== "string" || next.data.length === 0) return
|
||||
callback(next.data)
|
||||
})
|
||||
}
|
||||