Compare commits

..

102 Commits

Author SHA1 Message Date
opencode 6bfccace0c release: v1.0.39 2025-11-07 01:04:05 +00:00
Dax Raad b25d4f9dfb fix issue with input randomly breaking 2025-11-06 19:48:10 -05:00
GitHub Action d1962ca5a7 chore: format code 2025-11-07 00:14:59 +00:00
Dax Raad 25f31f3096 codex tweaks 2025-11-06 19:14:01 -05:00
opencode 11a6f0886e release: v1.0.37 2025-11-06 23:14:32 +00:00
Dax 3ba7e243d0 system theme (#4010) 2025-11-06 18:00:09 -05:00
Nicolai van der Smagt a2ab019317 fix: resolve Mistral API compatibility issues (#2440)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-06 16:53:02 -06:00
Jay V 21957406ff docs: add Deep Infra provider documentation 2025-11-06 17:08:55 -05:00
Adam 61c4747fbe fix(desktop): diff highlight rendering 2025-11-06 15:58:45 -06:00
Adam 957c43aa09 fix(desktop): review tab padding 2025-11-06 15:34:47 -06:00
Adam 96c57418f3 feat(desktop): review flow 2025-11-06 15:13:06 -06:00
Shanjai Raj b8c51e307f Fix: Auto-scroll to bottom when selecting session from list (#3988)
Co-authored-by: shanjairaj7 <shanjairaj7@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-06 14:29:39 -06:00
Frank 6791233ca0 Add desktop-feedback 2025-11-06 14:29:11 -05:00
GitHub Action cd6072ec58 chore: format code 2025-11-06 18:59:34 +00:00
Jay V 017e42bbcd docs: align web package favicon with console design for consistency 2025-11-06 13:58:52 -05:00
opencode 2d20582802 release: v1.0.36 2025-11-06 18:55:54 +00:00
GitHub Action 2bcc00dbf0 chore: format code 2025-11-06 18:21:59 +00:00
Dax Raad e45e94634f only display last 100 messages in TUI 2025-11-06 13:21:15 -05:00
Aiden Cline de1278414f fix: opencode run not parsing model string 2025-11-06 12:09:13 -06:00
Dax Raad 3c2803fd9a flaky test 2025-11-06 13:05:53 -05:00
GitHub Action 90c2b26733 chore: format code 2025-11-06 18:03:50 +00:00
Dax Raad 1ea3a8eb9b big format 2025-11-06 13:03:12 -05:00
Aiden Cline 8729edc5e0 update import command to accept share links 2025-11-06 11:55:57 -06:00
Aiden Cline d8bcf1f5f3 ci: update auto label 2025-11-06 11:46:42 -06:00
Dax Raad 67f3c934fe fix tests 2025-11-06 11:42:46 -05:00
Aiden Cline 065f656fb0 chore: rm hanging test 2025-11-06 10:39:32 -06:00
Dax Raad f636d937c4 fix undo command breaking other commands 2025-11-06 11:25:37 -05:00
Adam 492bf51a0d fix(desktop): sidebar collapsed width 2025-11-06 09:56:46 -06:00
Adam 81ab127f63 fix(desktop): demo type error 2025-11-06 09:49:39 -06:00
Adam 6ba7c54bab feat(desktop): collapsible sidebar 2025-11-06 09:48:51 -06:00
Adam 146bae82cb fix(desktop): button styles 2025-11-06 09:48:50 -06:00
Adam ab345cf0da feat(desktop): better tooltips 2025-11-06 09:48:50 -06:00
GitHub Action a1836527ce ignore: update download stats 2025-11-06 2025-11-06 12:04:56 +00:00
opencode-agent[bot] 49e4cfb286 Added big dot (●) indicator for current session in modal (#3980)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-06 01:09:59 -06:00
Frank e52bfab79d Update sst 2025-11-06 05:42:25 +00:00
opencode cc6d5c8ddd release: v1.0.35 2025-11-06 05:42:24 +00:00
Dax Raad afe8508949 fix homebrew upgrade 2025-11-06 00:37:44 -05:00
opencode 7c098c8849 release: v1.0.34 2025-11-06 02:35:17 +00:00
Dax Raad 11d6005b77 tui: reduce scrollbar visual prominence for less distracting interface 2025-11-05 21:30:40 -05:00
Dax Raad 2cc072b3dc enable scrollbar 2025-11-05 21:27:23 -05:00
opencode 86247b8ea9 release: v1.0.33 2025-11-06 02:18:41 +00:00
Dax Raad 0a5a02043c tui: move debug shortcuts to command palette for better discoverability 2025-11-05 21:13:35 -05:00
Sebastian Herrlinger 6e553f7e20 upgrade to opentui v0.1.36 2025-11-06 01:43:35 +00:00
opencode bb6acc0ec6 release: v1.0.32 2025-11-06 01:43:35 +00:00
Dax Raad 5a84b9f467 temporarily use strip-ansi package till bun bug is fixed 2025-11-05 20:38:37 -05:00
opencode c7031dfd77 release: v1.0.31 2025-11-06 01:35:40 +00:00
Dax Raad e136a40771 ignore tmp type rrror 2025-11-05 20:31:13 -05:00
Dax Raad ef25650ced regen bunlock 2025-11-05 20:30:40 -05:00
Dax Raad 6555a33eff type errors 2025-11-05 20:14:31 -05:00
Dax Raad 247ce44776 fix log 2025-11-05 20:01:57 -05:00
Dax Raad 4e7bfaab8b fix log 2025-11-05 20:00:09 -05:00
Sebastian Herrlinger 8b26a1f9bd upgrade to opentui 0.1.35, mitigating disappearing content and crashes 2025-11-06 01:38:58 +01:00
Dax Raad 2a9b6a85de core: ensure export command output can be piped without UI interference 2025-11-05 18:36:06 -05:00
opencode c9ae89a38b release: v1.0.30 2025-11-05 23:32:22 +00:00
Dax Raad e316050bf5 temporarily remove bun strip ansi due to bug 2025-11-05 18:27:19 -05:00
Dax Raad 306f45f04a add opencode import command to restore sessions from JSON exports 2025-11-05 18:05:01 -05:00
Adam e006e3355c feat(desktop): incrementally load sessions in side nav 2025-11-05 16:32:08 -06:00
opencode d7e31f76c4 release: v1.0.29 2025-11-05 22:14:46 +00:00
Aiden Cline d425723249 ask instead of throwing tool error if file is outside cwd 2025-11-05 16:09:47 -06:00
Adam c59ec71918 fix(desktop): max height on message diffs, session stats 2025-11-05 16:00:20 -06:00
Dax Raad 05ae99a09b fix sidebar modified files 2025-11-05 16:49:17 -05:00
opencode 6e22b45905 release: v1.0.28 2025-11-05 21:08:58 +00:00
Aiden Cline c664f92829 acp: update auth method 2025-11-05 15:01:14 -06:00
Aiden Cline f95333aaa4 acp: default to big pickle 2025-11-05 14:59:02 -06:00
opencode ef0b5e3dcb release: v1.0.27 2025-11-05 20:44:40 +00:00
Dax Raad b7262b8527 performance improvements 2025-11-05 15:33:23 -05:00
Dax Raad 1f44c7f750 include file count in summary 2025-11-05 14:23:10 -05:00
Dax Raad 7dba570195 ci: fix aur package 2025-11-05 14:22:25 -05:00
Haris Gušić 3d04ba26a3 fix(autocomplete): Prioritize exact matches (#3760) 2025-11-05 12:25:05 -06:00
Adam 3660e2c481 fix(desktop): local dev url 2025-11-05 12:09:30 -06:00
opencode 06ca45189b release: v1.0.26 2025-11-05 18:08:19 +00:00
Adam 674febcf60 fix(desktop): type issue 2025-11-05 11:59:10 -06:00
Adam 582d9a9622 fix(desktop): type issue 2025-11-05 11:56:32 -06:00
Adam d525fbf829 feat(desktop): session router, interrupt agent, visual cleanup 2025-11-05 11:55:35 -06:00
OpeOginni 69a499f807 fix(tui): restructure Sidebar component to be scrollable (#3946) 2025-11-05 11:33:30 -06:00
Dax Raad 37e564139f tui: lower paste summary threshold to trigger on shorter content 2025-11-05 12:32:23 -05:00
monke-yo ee8b81269b feat: add --attach flag to opencode run (#3889) 2025-11-05 11:31:01 -06:00
Filip 53998a2fed chore: remove unused patch tool from registry (to avoid accidental inclusions of it) (#3938) 2025-11-05 11:26:28 -06:00
Aiden Cline af7b9e77d1 fix: eu-west-2 aws bedrock issue 2025-11-05 10:14:08 -06:00
Aiden Cline 77c65b18b5 tweak: normalize escape keybind 2025-11-05 10:13:04 -06:00
Matthew Fitzpatrick c9dfe6d964 docs: include "limit" example (#3925)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2025-11-05 09:47:11 -06:00
Aiden Cline 03f7f18260 ci: adjust auto label 2025-11-05 09:36:38 -06:00
GitHub Action 2db76fc6dd ignore: update download stats 2025-11-05 2025-11-05 12:04:41 +00:00
opencode 7269c2316d release: v1.0.25 2025-11-05 07:00:07 +00:00
Aiden Cline 1e0596bc46 ACP: update package, fix slash command bug (#3906) 2025-11-05 00:50:48 -06:00
Aiden Cline 3ebec2435a allow @ agents to work even if not first thing in prompt 2025-11-04 23:37:09 -06:00
Christian Stewart b90c0b5fac feat(tui): add /export and /copy commands (#3883)
Signed-off-by: Christian Stewart <christian@aperture.us>
2025-11-04 23:02:45 -06:00
Err 3b1ab444fd feat: add Clojure syntax highlighting support (#3912)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2025-11-04 22:47:05 -06:00
Dax Raad 234db24f1f tui: fix command validation to prevent invalid commands from being executed 2025-11-04 20:46:01 -05:00
Kyle F Butts 04546c0873 Add support for R formatter in formatters (#3918)
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-04 18:18:33 -06:00
opencode f51bd91af4 release: v1.0.24 2025-11-05 00:12:19 +00:00
Dax Raad ebca25462e tui: fix session abort when autocomplete is visible 2025-11-04 18:45:39 -05:00
Aiden Cline 01b9148c04 fix: image reading error, also add error toast for event bus 2025-11-04 17:30:58 -06:00
Frank d3e080894c wip: zen 2025-11-04 17:54:08 -05:00
Jay V ee9aa24a55 ignore: update meta description to use proper OpenCode capitalization 2025-11-04 17:27:22 -05:00
Frank 16e2bded5b wip: zen 2025-11-04 17:24:20 -05:00
Frank 9fb49ab87b wip: zen 2025-11-04 17:24:20 -05:00
Frank 8d6a03cc89 zen: custom reload amount 2025-11-04 17:24:20 -05:00
Dax Raad 71b04ffa99 add command bar option to interrupt session 2025-11-04 17:11:07 -05:00
Aiden Cline 678ca757c9 fix: permissions not responding to esc 2025-11-04 16:08:31 -06:00
Jared A. Scheel 272349b8da Add support for uv format in formatters (#3916) 2025-11-04 15:40:29 -06:00
Ola fe94bb8e50 feat(provider): add GitHub Enterprise support for Copilot (#2522)
Co-authored-by: Jon-Mikkel Korsvik <48263282+jkorsvik@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
2025-11-04 15:36:12 -06:00
280 changed files with 7374 additions and 5068 deletions
+2 -2
View File
@@ -20,8 +20,8 @@ jobs:
const title = issue.title;
const description = issue.body || '';
// Check for web/desktop keywords
const webPattern = /\b(web|desktop)\b/i;
// Check for "opencode web" keyword
const webPattern = /(opencode web)/i;
const isWebRelated = webPattern.test(title) || webPattern.test(description);
// Check for version patterns like v1.0.x or 1.0.x
+23 -23
View File
@@ -21,27 +21,27 @@
json
{
"recipient_name": "multi_tool_use.parallel",
"parameters": {
"tool_uses": [
{
"recipient_name": "functions.read",
"parameters": {
"filePath": "path/to/file.tsx"
}
},
{
"recipient_name": "functions.read",
"parameters": {
"filePath": "path/to/file.ts"
}
},
{
"recipient_name": "functions.read",
"parameters": {
"filePath": "path/to/file.md"
}
}
]
}
"recipient_name": "multi_tool_use.parallel",
"parameters": {
"tool_uses": [
{
"recipient_name": "functions.read",
"parameters": {
"filePath": "path/to/file.tsx"
}
},
{
"recipient_name": "functions.read",
"parameters": {
"filePath": "path/to/file.ts"
}
},
{
"recipient_name": "functions.read",
"parameters": {
"filePath": "path/to/file.md"
}
}
]
}
}
+2
View File
@@ -130,3 +130,5 @@
| 2025-11-02 | 644,067 (+7,967) | 590,004 (+8,198) | 1,234,071 (+16,165) |
| 2025-11-03 | 653,130 (+9,063) | 597,139 (+7,135) | 1,250,269 (+16,198) |
| 2025-11-04 | 663,912 (+10,782) | 608,056 (+10,917) | 1,271,968 (+21,699) |
| 2025-11-05 | 675,074 (+11,162) | 619,690 (+11,634) | 1,294,764 (+22,796) |
| 2025-11-06 | 686,252 (+11,178) | 630,885 (+11,195) | 1,317,137 (+22,373) |
+153 -130
View File
@@ -11,7 +11,7 @@
"@tsconfig/bun": "catalog:",
"husky": "9.1.7",
"prettier": "3.6.2",
"sst": "3.17.19",
"sst": "3.17.22",
"turbo": "2.5.6",
},
},
@@ -39,7 +39,7 @@
},
"packages/console/core": {
"name": "@opencode-ai/console-core",
"version": "1.0.23",
"version": "1.0.39",
"dependencies": {
"@aws-sdk/client-sts": "3.782.0",
"@jsx-email/render": "1.1.1",
@@ -66,7 +66,7 @@
},
"packages/console/function": {
"name": "@opencode-ai/console-function",
"version": "1.0.23",
"version": "1.0.39",
"dependencies": {
"@ai-sdk/anthropic": "2.0.0",
"@ai-sdk/openai": "2.0.2",
@@ -90,7 +90,7 @@
},
"packages/console/mail": {
"name": "@opencode-ai/console-mail",
"version": "1.0.23",
"version": "1.0.39",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
@@ -111,7 +111,7 @@
},
"packages/desktop": {
"name": "@opencode-ai/desktop",
"version": "1.0.23",
"version": "1.0.39",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -121,6 +121,7 @@
"@solid-primitives/event-bus": "1.1.2",
"@solid-primitives/resize-observer": "2.1.3",
"@solid-primitives/scroll": "2.1.3",
"@solid-primitives/storage": "4.3.3",
"@solidjs/meta": "catalog:",
"@solidjs/router": "0.15.3",
"@thisbeyond/solid-dnd": "0.7.5",
@@ -150,7 +151,7 @@
},
"packages/function": {
"name": "@opencode-ai/function",
"version": "1.0.23",
"version": "1.0.39",
"dependencies": {
"@octokit/auth-app": "8.0.1",
"@octokit/rest": "22.0.0",
@@ -166,14 +167,14 @@
},
"packages/opencode": {
"name": "opencode",
"version": "1.0.23",
"version": "1.0.39",
"bin": {
"opencode": "./bin/opencode",
},
"dependencies": {
"@actions/core": "1.11.1",
"@actions/github": "6.0.1",
"@agentclientprotocol/sdk": "0.4.9",
"@agentclientprotocol/sdk": "0.5.1",
"@clack/prompts": "1.0.0-alpha.1",
"@hono/standard-validator": "0.1.5",
"@hono/zod-validator": "catalog:",
@@ -184,8 +185,8 @@
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/script": "workspace:*",
"@opencode-ai/sdk": "workspace:*",
"@opentui/core": "0.1.33",
"@opentui/solid": "0.1.33",
"@opentui/core": "0.0.0-20251106-788e97e4",
"@opentui/solid": "0.0.0-20251106-788e97e4",
"@parcel/watcher": "2.5.1",
"@pierre/precision-diffs": "catalog:",
"@solid-primitives/event-bus": "1.1.2",
@@ -207,6 +208,7 @@
"partial-json": "0.1.7",
"remeda": "catalog:",
"solid-js": "catalog:",
"strip-ansi": "7.1.2",
"tree-sitter-bash": "0.25.0",
"turndown": "7.2.0",
"ulid": "catalog:",
@@ -243,7 +245,7 @@
},
"packages/plugin": {
"name": "@opencode-ai/plugin",
"version": "1.0.23",
"version": "1.0.39",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"zod": "catalog:",
@@ -263,7 +265,7 @@
},
"packages/sdk/js": {
"name": "@opencode-ai/sdk",
"version": "1.0.23",
"version": "1.0.39",
"devDependencies": {
"@hey-api/openapi-ts": "0.81.0",
"@tsconfig/node22": "catalog:",
@@ -274,7 +276,7 @@
},
"packages/slack": {
"name": "@opencode-ai/slack",
"version": "1.0.23",
"version": "1.0.39",
"dependencies": {
"@opencode-ai/sdk": "workspace:*",
"@slack/bolt": "^3.17.1",
@@ -287,7 +289,7 @@
},
"packages/ui": {
"name": "@opencode-ai/ui",
"version": "1.0.23",
"version": "1.0.39",
"dependencies": {
"@kobalte/core": "catalog:",
"@opencode-ai/sdk": "workspace:*",
@@ -317,7 +319,7 @@
},
"packages/web": {
"name": "@opencode-ai/web",
"version": "1.0.23",
"version": "1.0.39",
"dependencies": {
"@astrojs/cloudflare": "12.6.3",
"@astrojs/markdown-remark": "6.3.1",
@@ -351,7 +353,6 @@
"trustedDependencies": [
"sharp",
"esbuild",
"tree-sitter",
"web-tree-sitter",
"tree-sitter-bash",
],
@@ -364,7 +365,7 @@
"@hono/zod-validator": "0.4.2",
"@kobalte/core": "0.13.11",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/precision-diffs": "0.4.1",
"@pierre/precision-diffs": "0.4.4",
"@solidjs/meta": "0.29.4",
"@tailwindcss/vite": "4.1.11",
"@tsconfig/bun": "1.0.9",
@@ -401,7 +402,7 @@
"@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.4.9", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-ExwH828LaTGoTTjxuw49l+fwOLA+Yx0+qkWn1TcHMOsY5mVI9CUfkj7ZDhv2klgZ7mJeT+lxX/Dn/KINv1AkNQ=="],
"@agentclientprotocol/sdk": ["@agentclientprotocol/sdk@0.5.1", "", { "dependencies": { "zod": "^3.0.0" } }, "sha512-9bq2TgjhLBSUSC5jE04MEe+Hqw8YePzKghhYZ9QcjOyonY3q2oJfX6GoSO83hURpEnsqEPIrex6VZN3+61fBJg=="],
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@2.2.10", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icLGO7Q0NinnHIPgT+y1QjHVwH4HwV+brWbvM+FfCG2Afpa89PyKa3Ret91kGjZpBgM/xnj1B7K5eM+rRlsXQA=="],
@@ -571,7 +572,7 @@
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="],
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.7.8", "", { "peerDependencies": { "unenv": "2.0.0-rc.21", "workerd": "^1.20250927.0" }, "optionalPeers": ["workerd"] }, "sha512-Ky929MfHh+qPhwCapYrRPwPVHtA2Ioex/DbGZyskGyNRDe9Ru3WThYZivyNVaPy5ergQSgMs9OKrM9Ajtz9F6w=="],
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.7.9", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "^1.20250927.0" }, "optionalPeers": ["workerd"] }, "sha512-Drm7qlTKnvncEv+DANiQNEonq0H0LyIsoFZYJ6tJ8OhAoy5udIE8yp6BsVDYcIjcYLIybp4M7c/P7ly/56SoHg=="],
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20251011.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-0DirVP+Z82RtZLlK2B+VhLOkk+ShBqDYO/jhcRw4oVlp0TOvk3cOVZChrt3+y3NV8Y/PYgTEywzLKFSziK4wCg=="],
@@ -601,7 +602,7 @@
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
"@emnapi/runtime": ["@emnapi/runtime@1.6.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA=="],
"@emnapi/runtime": ["@emnapi/runtime@1.7.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q=="],
"@emotion/is-prop-valid": ["@emotion/is-prop-valid@0.8.8", "", { "dependencies": { "@emotion/memoize": "0.7.4" } }, "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA=="],
@@ -611,57 +612,57 @@
"@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.11", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.11", "", { "os": "android", "cpu": "arm" }, "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.11", "", { "os": "android", "cpu": "arm64" }, "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.11", "", { "os": "android", "cpu": "x64" }, "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.11", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.11", "", { "os": "linux", "cpu": "arm" }, "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.11", "", { "os": "linux", "cpu": "ia32" }, "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.11", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.11", "", { "os": "linux", "cpu": "none" }, "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.11", "", { "os": "linux", "cpu": "s390x" }, "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.11", "", { "os": "linux", "cpu": "x64" }, "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.11", "", { "os": "none", "cpu": "x64" }, "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.11", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.11", "", { "os": "openbsd", "cpu": "x64" }, "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.11", "", { "os": "none", "cpu": "arm64" }, "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.11", "", { "os": "sunos", "cpu": "x64" }, "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.11", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.11", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
"@expressive-code/core": ["@expressive-code/core@0.41.3", "", { "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-9qzohqU7O0+JwMEEgQhnBPOw5DtsQRBXhW++5fvEywsuX44vCGGof1SL5OvPElvNgaWZ4pFZAFSlkNOkGyLwSQ=="],
@@ -961,21 +962,21 @@
"@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
"@opentui/core": ["@opentui/core@0.1.33", "", { "dependencies": { "bun-ffi-structs": "^0.1.0", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.1.33", "@opentui/core-darwin-x64": "0.1.33", "@opentui/core-linux-arm64": "0.1.33", "@opentui/core-linux-x64": "0.1.33", "@opentui/core-win32-arm64": "0.1.33", "@opentui/core-win32-x64": "0.1.33", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-vwHdrPIqnsY6YnG2JTNhenHSsx+HUPYrQTBZdmEfCj9ROGVzKgUKbSDH1xGK2OtSNRb2KVBg4XaMpq0bie6afQ=="],
"@opentui/core": ["@opentui/core@0.0.0-20251106-788e97e4", "", { "dependencies": { "bun-ffi-structs": "^0.1.0", "jimp": "1.6.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@dimforge/rapier2d-simd-compat": "^0.17.3", "@opentui/core-darwin-arm64": "0.0.0-20251106-788e97e4", "@opentui/core-darwin-x64": "0.0.0-20251106-788e97e4", "@opentui/core-linux-arm64": "0.0.0-20251106-788e97e4", "@opentui/core-linux-x64": "0.0.0-20251106-788e97e4", "@opentui/core-win32-arm64": "0.0.0-20251106-788e97e4", "@opentui/core-win32-x64": "0.0.0-20251106-788e97e4", "bun-webgpu": "0.1.3", "planck": "^1.4.2", "three": "0.177.0" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-Es2Oe7/J/yb58e0jjq/04pV9Mekx6hM4go4C5uTiZksX3asfIGWk553cuf5WlWj0PDlVnC+s7Nnayi/NbLJ5jQ=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.1.33", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JBvzcP2V7fT9KxFAMenHRd/t72qPP5IL5kzge2uok1T7t2nw3Wa+CWI5s6FYP42p2b1W9qZkv5Fno5gA7OAYuQ=="],
"@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.0.0-20251106-788e97e4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EOO8SSIYJBIh+Sd9bgVTiQmt+TEJmfg65/oym54J4zfDtCYlAqSaLcRnDe4TzB+4hejV9of8etrG3ZZACBJT+A=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.1.33", "", { "os": "darwin", "cpu": "x64" }, "sha512-x7DY6VCkAky10z/2o4UkkuNW/nIvoX7uAh3dJOHWZCLbiKywSFvFk3QZVVcH5BMk4tOOophYTzika4s4HpaeMg=="],
"@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.0.0-20251106-788e97e4", "", { "os": "darwin", "cpu": "x64" }, "sha512-MUTt7CDbzL2afNGK8gJ4jUZd+AHiOUJEO0eJGDSfWU8DUs0zv8XoLZfaI5PPbkUPEL/7CEBMARAAiwfRtoG/4A=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.1.33", "", { "os": "linux", "cpu": "arm64" }, "sha512-bBc1EdkVxsLBtqGjXM2BYpBJLa57ogcrSADSZbc5cQkPu0muSGzUwBbVnVZJUjWEfk6n5jcd4dDmLezVoQga0A=="],
"@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.0.0-20251106-788e97e4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Zi1EzLCzooRfYoQnN/Dz8OxzrpRXByny8SJqhdO9ZP2mYX72yJ3AhUUW1Sl6YSzVi0H+QIKj7g+RX2KfsXIGFg=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.1.33", "", { "os": "linux", "cpu": "x64" }, "sha512-3oVL5mrLlKLUc1lc4v7xS3BJ9N7PnnimbGwAvlnVpfaAygotAs1XkPcjsUe6ItMnSJyi0FWiDHUE2+GiDtM5Nw=="],
"@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.0.0-20251106-788e97e4", "", { "os": "linux", "cpu": "x64" }, "sha512-/E0XEBVzO4JEEhJGzfURF2tPxDE2oTODxlgNYYB1QbAuOsLcV69uSrwAjo1TxuIn4P78tBR+ZOlmONjroPqfbQ=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.1.33", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q68v7wssE+r0OG1KIGfi7m3fnu8KOK4ZNg9ML6EwE47VF9/bqgUe+6fPiXh5mmHzTwof7nAOdXCf052av5/upQ=="],
"@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.0.0-20251106-788e97e4", "", { "os": "win32", "cpu": "arm64" }, "sha512-En/29cgpYVvzlrQ7fAoP+EUdzmczgMzBIGGM0RuLi2hmCmCqyMtOJ0EJUh9UXa5jYIXNGOP49sIP6bUBbvXt7g=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.1.33", "", { "os": "win32", "cpu": "x64" }, "sha512-PvuchmUnbMCUXXMzfle/WTzhNGIdJ6RGCCoclx3YVUyNUVuUicPf42OEV+td2m81/Hr3CgcLn98HYX1TLIzPrw=="],
"@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.0.0-20251106-788e97e4", "", { "os": "win32", "cpu": "x64" }, "sha512-2lu0bgEi+k/1c9VHQFg3wjVxMgQnuZhs/6sDDpxk9eNS3fuHEJfZi0PFJQk2J4IFQL61nzukOvJKgYDWQvKB1g=="],
"@opentui/solid": ["@opentui/solid@0.1.33", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.1.33", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-bWSALdGJ2j51zwZ2gK1ZIBxFgauHq+V1ejEnyd4XamYMdWfpAKU+AUWDVLbpx1T9XG1oAnycJZfYX7BsZdVOOg=="],
"@opentui/solid": ["@opentui/solid@0.0.0-20251106-788e97e4", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.0.0-20251106-788e97e4", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.9", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.9" } }, "sha512-82rFS6BB60rJZU5Ad8Wf58V6HaMSkpnjciizkv3vsjJc9hvIAwLRNYqPypQB+etypuELhYMzzaVqt+wUsPHSqQ=="],
"@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="],
@@ -1033,7 +1034,7 @@
"@petamoriken/float16": ["@petamoriken/float16@3.9.3", "", {}, "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g=="],
"@pierre/precision-diffs": ["@pierre/precision-diffs@0.4.1", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/transformers": "3.13.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.13.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-AoozHakINGyNJFgbYc/1PlDK0yunrAxbtXEMBe9fdu8RLkNjVtYRTLw7EF2mM/YuVoVRjj2HT/2VJ4a2rMyDOA=="],
"@pierre/precision-diffs": ["@pierre/precision-diffs@0.4.4", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/transformers": "3.14.0", "diff": "8.0.2", "fast-deep-equal": "3.1.3", "hast-util-to-html": "9.0.5", "shiki": "3.14.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-9bhWs+hsz1i0/SMIrzce+fFrSec8aLIFrJYTGHATlynmQovngIWz1Gc+XwGigvY4+zSMksrGPzO5HiaNlvRqtQ=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
@@ -1041,7 +1042,7 @@
"@poppinss/colors": ["@poppinss/colors@4.1.5", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw=="],
"@poppinss/dumper": ["@poppinss/dumper@0.6.4", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-iG0TIdqv8xJ3Lt9O8DrPRxw1MRLjNpoqiSGU03P/wNLP/s0ra0udPJ1J2Tx5M0J3H/cVyEgpbn8xUKRY9j59kQ=="],
"@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="],
"@poppinss/exception": ["@poppinss/exception@1.2.2", "", {}, "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg=="],
@@ -1187,7 +1188,7 @@
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
"@sindresorhus/is": ["@sindresorhus/is@7.1.0", "", {}, "sha512-7F/yz2IphV39hiS2zB4QYVkivrptHHh0K8qJJd9HhuWSdvf8AN7NpebW3CcDZDBQsUPMoDKWsY2WWgW7bqOcfA=="],
"@sindresorhus/is": ["@sindresorhus/is@7.1.1", "", {}, "sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ=="],
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="],
@@ -1205,7 +1206,7 @@
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.4", "", { "dependencies": { "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-Z4DUr/AkgyFf1bOThW2HwzREagee0sB5ycl+hDiSZOfRLW8ZgrOjDi6g8mHH19yyU5E2A/64W3z6SMIf5XiUSQ=="],
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.1", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.4", "@smithy/util-middleware": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-BciDJ5hkyYEGBBKMbjGB1A/Zq8bYZ41Zo9BMnGdKF6QD1fY4zIkYx6zui/0CHaVGnv6h0iy8y4rnPX9CPCAPyQ=="],
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.2", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.4", "@smithy/util-middleware": "^4.2.4", "tslib": "^2.6.2" } }, "sha512-4Jys0ni2tB2VZzgslbEgszZyMdTkPOFGA8g+So/NjR8oy6Qwaq4eSwsrRI+NMtb0Dq4kqCzGUu/nGUx7OM/xfw=="],
"@smithy/core": ["@smithy/core@3.17.2", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.4", "@smithy/protocol-http": "^5.3.4", "@smithy/types": "^4.8.1", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.4", "@smithy/util-stream": "^4.5.5", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-n3g4Nl1Te+qGPDbNFAYf+smkRVB+JhFsGy9uJXXZQEufoP4u0r+WLh6KvTDolCswaagysDc/afS1yvb2jnj1gQ=="],
@@ -1267,7 +1268,7 @@
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.5", "", { "dependencies": { "@smithy/property-provider": "^4.2.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-GwaGjv/QLuL/QHQaqhf/maM7+MnRFQQs7Bsl6FlaeK6lm6U7mV5AAnVabw68cIoMl5FQFyKK62u7RWRzWL25OQ=="],
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.7", "", { "dependencies": { "@smithy/config-resolver": "^4.4.1", "@smithy/credential-provider-imds": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/property-provider": "^4.2.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-6hinjVqec0WYGsqN7h9hL/ywfULmJJNXGXnNZW7jrIn/cFuC/aVlVaiDfBIJEvKcOrmN8/EgsW69eY0gXABeHw=="],
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.8", "", { "dependencies": { "@smithy/config-resolver": "^4.4.2", "@smithy/credential-provider-imds": "^4.2.4", "@smithy/node-config-provider": "^4.3.4", "@smithy/property-provider": "^4.2.4", "@smithy/smithy-client": "^4.9.2", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-gIoTf9V/nFSIZ0TtgDNLd+Ws59AJvijmMDYrOozoMHPJaG9cMRdqNO50jZTlbM6ydzQYY8L/mQ4tKSw/TB+s6g=="],
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.4", "@smithy/types": "^4.8.1", "tslib": "^2.6.2" } }, "sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg=="],
@@ -1309,6 +1310,8 @@
"@solid-primitives/static-store": ["@solid-primitives/static-store@0.1.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw=="],
"@solid-primitives/storage": ["@solid-primitives/storage@4.3.3", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "@tauri-apps/plugin-store": "*", "solid-js": "^1.6.12" }, "optionalPeers": ["@tauri-apps/plugin-store"] }, "sha512-ACbNwMZ1s8VAvld6EUXkDkX/US3IhtlPLxg6+B2s9MwNUugwdd51I98LPEaHrdLpqPmyzqgoJe0TxEFlf3Dqrw=="],
"@solid-primitives/trigger": ["@solid-primitives/trigger@1.2.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-IWoptVc0SWYgmpBPpCMehS5b07+tpFcvw15tOQ3QbXedSYn6KP8zCjPkHNzMxcOvOicTneleeZDP7lqmz+PQ6g=="],
"@solid-primitives/utils": ["@solid-primitives/utils@6.3.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ=="],
@@ -1319,7 +1322,7 @@
"@solidjs/start": ["@solidjs/start@1.2.0", "", { "dependencies": { "@tanstack/server-functions-plugin": "1.121.21", "@vinxi/plugin-directives": "^0.5.0", "@vinxi/server-components": "^0.5.0", "cookie-es": "^2.0.0", "defu": "^6.1.2", "error-stack-parser": "^2.1.4", "html-to-image": "^1.11.11", "radix3": "^1.1.0", "seroval": "^1.0.2", "seroval-plugins": "^1.0.2", "shiki": "^1.26.1", "source-map-js": "^1.0.2", "terracotta": "^1.0.4", "tinyglobby": "^0.2.2", "vite-plugin-solid": "^2.11.1" }, "peerDependencies": { "vinxi": "^0.5.7" } }, "sha512-SRv1g3R+4sxZnxCBPK1IedtLKsPhPJ7W/Yv4xEHjM4jJGPWi3ed35/yd0D5zhRK0C7zJIkZKbhnR/S3g8JUD5w=="],
"@speed-highlight/core": ["@speed-highlight/core@1.2.8", "", {}, "sha512-IGytNtnUnPIobIbOq5Y6LIlqiHNX+vnToQIS7lj6L5819C+rA8TXRDkkG8vePsiBOGcoW9R6i+dp2YBUKdB09Q=="],
"@speed-highlight/core": ["@speed-highlight/core@1.2.12", "", {}, "sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA=="],
"@standard-community/standard-json": ["@standard-community/standard-json@0.3.5", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "@types/json-schema": "^7.0.15", "@valibot/to-json-schema": "^1.3.0", "arktype": "^2.1.20", "effect": "^3.16.8", "quansync": "^0.2.11", "sury": "^10.0.0", "typebox": "^1.0.17", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.24.5" }, "optionalPeers": ["@valibot/to-json-schema", "arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-to-json-schema"] }, "sha512-4+ZPorwDRt47i+O7RjyuaxHRK/37QY/LmgxlGrRrSTLYoFatEOzvqIc85GTlM18SFZ5E91C+v0o/M37wZPpUHA=="],
@@ -1447,7 +1450,7 @@
"@types/scheduler": ["@types/scheduler@0.26.0", "", {}, "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA=="],
"@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
"@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
@@ -1589,7 +1592,7 @@
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
"axios": ["axios@1.13.1", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw=="],
"axios": ["axios@1.13.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
@@ -1617,13 +1620,13 @@
"bare-stream": ["bare-stream@2.7.0", "", { "dependencies": { "streamx": "^2.21.0" }, "peerDependencies": { "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-buffer", "bare-events"] }, "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A=="],
"bare-url": ["bare-url@2.3.1", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-v2yl0TnaZTdEnelkKtXZGnotiV6qATBlnNuUMrHl6v9Lmmrh9mw9RYyImPU7/4RahumSwQS1k2oKXcRfXcbjJw=="],
"bare-url": ["bare-url@2.3.2", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw=="],
"base-64": ["base-64@1.0.0", "", {}, "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.21", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.25", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA=="],
"bcp-47": ["bcp-47@2.1.0", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w=="],
@@ -1701,7 +1704,7 @@
"camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
"caniuse-lite": ["caniuse-lite@1.0.30001752", "", {}, "sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g=="],
"caniuse-lite": ["caniuse-lite@1.0.30001753", "", {}, "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw=="],
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
@@ -1919,7 +1922,7 @@
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"electron-to-chromium": ["electron-to-chromium@1.5.244", "", {}, "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw=="],
"electron-to-chromium": ["electron-to-chromium@1.5.245", "", {}, "sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ=="],
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
@@ -1961,7 +1964,7 @@
"esast-util-from-js": ["esast-util-from-js@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="],
"esbuild": ["esbuild@0.25.11", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.11", "@esbuild/android-arm": "0.25.11", "@esbuild/android-arm64": "0.25.11", "@esbuild/android-x64": "0.25.11", "@esbuild/darwin-arm64": "0.25.11", "@esbuild/darwin-x64": "0.25.11", "@esbuild/freebsd-arm64": "0.25.11", "@esbuild/freebsd-x64": "0.25.11", "@esbuild/linux-arm": "0.25.11", "@esbuild/linux-arm64": "0.25.11", "@esbuild/linux-ia32": "0.25.11", "@esbuild/linux-loong64": "0.25.11", "@esbuild/linux-mips64el": "0.25.11", "@esbuild/linux-ppc64": "0.25.11", "@esbuild/linux-riscv64": "0.25.11", "@esbuild/linux-s390x": "0.25.11", "@esbuild/linux-x64": "0.25.11", "@esbuild/netbsd-arm64": "0.25.11", "@esbuild/netbsd-x64": "0.25.11", "@esbuild/openbsd-arm64": "0.25.11", "@esbuild/openbsd-x64": "0.25.11", "@esbuild/openharmony-arm64": "0.25.11", "@esbuild/sunos-x64": "0.25.11", "@esbuild/win32-arm64": "0.25.11", "@esbuild/win32-ia32": "0.25.11", "@esbuild/win32-x64": "0.25.11" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q=="],
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
"esbuild-plugin-copy": ["esbuild-plugin-copy@2.1.1", "", { "dependencies": { "chalk": "^4.1.2", "chokidar": "^3.5.3", "fs-extra": "^10.0.1", "globby": "^11.0.3" }, "peerDependencies": { "esbuild": ">= 0.14.0" } }, "sha512-Bk66jpevTcV8KMFzZI1P7MZKZ+uDcrZm2G2egZ2jNIvVnivDpodZI+/KnpL3Jnap0PBdIHU7HwFGB8r+vV5CVw=="],
@@ -2665,7 +2668,7 @@
"mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="],
"miniflare": ["miniflare@4.20251011.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251011.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-Qbw1Z8HTYM1adWl6FAtzhrj34/6dPRDPwdYOx21dkae8a/EaxbMzRIPbb4HKVGMVvtqbK1FaRCgDLVLolNzGHg=="],
"miniflare": ["miniflare@4.20251011.2", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "sharp": "^0.33.5", "stoppable": "1.1.0", "undici": "7.14.0", "workerd": "1.20251011.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-5oAaz6lqZus4QFwzEJiNtgpjZR2TBVwBeIhOW33V4gu+l23EukpKja831tFIX2o6sOD/hqZmKZHplOrWl3YGtQ=="],
"minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
@@ -2749,11 +2752,11 @@
"object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
"ofetch": ["ofetch@1.5.0", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-A7llJ7eZyziA5xq9//3ZurA8OhFqtS99K5/V1sLBJ5j137CM/OAjlbA/TEJXBuOWwOfLqih+oH5U3ran4za1FQ=="],
"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=="],
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
"oidc-token-hash": ["oidc-token-hash@5.1.1", "", {}, "sha512-D7EmwxJV6DsEB6vOFLrBM2OzsVgQzgPWyHlV2OOAVj772n+WTXpudC9e9u5BVKQnYwaD30Ivhi9b+4UeBcGu9g=="],
"oidc-token-hash": ["oidc-token-hash@5.2.0", "", {}, "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw=="],
"omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="],
@@ -3175,23 +3178,23 @@
"sqlstring": ["sqlstring@2.3.3", "", {}, "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg=="],
"sst": ["sst@3.17.19", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.19", "sst-darwin-x64": "3.17.19", "sst-linux-arm64": "3.17.19", "sst-linux-x64": "3.17.19", "sst-linux-x86": "3.17.19", "sst-win32-arm64": "3.17.19", "sst-win32-x64": "3.17.19", "sst-win32-x86": "3.17.19" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-j0FlQhFZW+QWCczzqfPr6fZAF0Um7lP1tbGdd7zkbjFlxdk9BUBI4CYXUnopC6KaTMtjvpfg3XRF7v0bDc9g+A=="],
"sst": ["sst@3.17.22", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.22", "sst-darwin-x64": "3.17.22", "sst-linux-arm64": "3.17.22", "sst-linux-x64": "3.17.22", "sst-linux-x86": "3.17.22", "sst-win32-arm64": "3.17.22", "sst-win32-x64": "3.17.22", "sst-win32-x86": "3.17.22" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-C+XMTbm6fx+7eT+ESAMATqG7qV7+pyVfxYQb6osdH3jd4u91QW1VU/xlEru+RU1rs1ZE58ixXdRP75UGPn+gog=="],
"sst-darwin-arm64": ["sst-darwin-arm64@3.17.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6FeEgPqXkRT3o5qV0xktJ1eUiscJiPLBcGaxOxIEClpkVggZM83hO7Nizx/cAaAMhr1XQhbOZcKYueDHPdUY+Q=="],
"sst-darwin-arm64": ["sst-darwin-arm64@3.17.22", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B2pKq1dWc60+7HfXQ6/9etskxxNv9axxlQKveCLQAuG2a3mmtv2/jcR0Ch3mvSTGtW+KfhzUXda2kj7nZ/phBA=="],
"sst-darwin-x64": ["sst-darwin-x64@3.17.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-/z78dxfLHG8FtOhpjMnYSpKSdQjfdyKbq+cL3eud2+g2BQr7IyQ8BWNGimk2oadh38V3r6dO1/5aVJh3x3l1rg=="],
"sst-darwin-x64": ["sst-darwin-x64@3.17.22", "", { "os": "darwin", "cpu": "x64" }, "sha512-flikYqXvhwwrS6x2FDOde+MQODHaZCIbUkVHYO3/gYo99rbAMQ8VpC/3LXnmnPEQkLOwWCSzLp4S4F9nG/PW2g=="],
"sst-linux-arm64": ["sst-linux-arm64@3.17.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-vbcMjiuLVxZ7352ajGlMqsS4J5AkAYvjLmsEALySUBVQhJUO9U7pk2P+Orfn702ZcO+6+NkGG9AL/g3K9EM1Tg=="],
"sst-linux-arm64": ["sst-linux-arm64@3.17.22", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pyD8Oej9js8XeCCebiEIde02vC5hc+bLl2/jR02K+9gYkGVJ6n5bkT8AlR8zWdS4FJKPyeJYUfjliT1T33j+g=="],
"sst-linux-x64": ["sst-linux-x64@3.17.19", "", { "os": "linux", "cpu": "x64" }, "sha512-gkNNmuHyvKjcb7RwMyoUH4wtgd7/bH7vUlMbcVsDzwt38y7+iTxyPMbcihucw42wDQRaDJtkDneSqj08U+MTFQ=="],
"sst-linux-x64": ["sst-linux-x64@3.17.22", "", { "os": "linux", "cpu": "x64" }, "sha512-A5p941edP9wgfgsbLUMeEPvi9JExj0OSaxgtFAC6/6BYoW4zruGAPzq206Ln6dNYP3gRdo5TJbSjio3F0ot8qg=="],
"sst-linux-x86": ["sst-linux-x86@3.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-Bsvunkh4onZRVv4Rxq7bT/63qQOg2KJoQKhAQtFkJdbri/cOA2QWkzqH8+pC5Sv9rSvbcIJAEIhMXILC0pqCJw=="],
"sst-linux-x86": ["sst-linux-x86@3.17.22", "", { "os": "linux", "cpu": "none" }, "sha512-pFDIi+ZwH8GOvy5He9wsbAjRGf/sTGhGE/V480w0A6itb9BC4jQ9sblJkk3Jx/fP2g27pKN2RNz+ifOU+GrUYQ=="],
"sst-win32-arm64": ["sst-win32-arm64@3.17.19", "", { "os": "win32", "cpu": "arm64" }, "sha512-dKxR4v24AODJLHiT9yNena0JUgyz3cHyCi6HZyxyG3dXyWncMe1ZXMXIgs1ZEUcU4XeYM2HVy+Nnz4KB1US1Kg=="],
"sst-win32-arm64": ["sst-win32-arm64@3.17.22", "", { "os": "win32", "cpu": "arm64" }, "sha512-9KaIrk+Z6hLDNi9GShf9NLrZi9jC/NNGpUAn6HvTXr8c6HUyQzg6takMH8nrISGCPn92y+IYWqdglaqbgnJTog=="],
"sst-win32-x64": ["sst-win32-x64@3.17.19", "", { "os": "win32", "cpu": "x64" }, "sha512-zgxSkGWZ1dewAr4R3slN/d3X9yumQDvAUOlJiX/6QE9Z67t/XNlow4+5i3L2oz4WHAFi59Un12YxbfM+RsBDmA=="],
"sst-win32-x64": ["sst-win32-x64@3.17.22", "", { "os": "win32", "cpu": "x64" }, "sha512-cvzyet4octGHK7w05jPUSPmUdlAWyh8IzjB8Pcs873K9AUGJEtQCftOKZjXaFdIG9DTvFWCCBi9zdzClxT9jJg=="],
"sst-win32-x86": ["sst-win32-x86@3.17.19", "", { "os": "win32", "cpu": "none" }, "sha512-z8S0kyb0ibz9Q3cNYDpcKYX47jys7j/mdebC8HUhtED1qKEAfqQ1vsR+zvWyN64Z9Ijj7aPi1KwNV6Et3d7F8g=="],
"sst-win32-x86": ["sst-win32-x86@3.17.22", "", { "os": "win32", "cpu": "none" }, "sha512-ol5icDJuHzG+AjbGbCIQoF8z3oiikTF9CtccdK/udqEF861DnngWzM99IY5TJvmJlN+38yOV0MY4XI5hM6SEQA=="],
"stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="],
@@ -3243,7 +3246,7 @@
"strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="],
"style-to-js": ["style-to-js@1.1.18", "", { "dependencies": { "style-to-object": "1.0.11" } }, "sha512-JFPn62D4kJaPTnhFUI244MThx+FEGbi+9dw1b9yBBQ+1CZpV7QAT8kUtJ7b7EUNdHajjF/0x8fT+16oLJoojLg=="],
"style-to-js": ["style-to-js@1.1.19", "", { "dependencies": { "style-to-object": "1.0.12" } }, "sha512-Ev+SgeqiNGT1ufsXyVC5RrJRXdrkRJ1Gol9Qw7Pb72YCKJXrBvP0ckZhBeVSrw2m06DJpei2528uIpjMb4TsoQ=="],
"style-to-object": ["style-to-object@1.0.12", "", { "dependencies": { "inline-style-parser": "0.2.6" } }, "sha512-ddJqYnoT4t97QvN2C95bCgt+m7AAgXjVnkk/jxAfmp7EAB8nnqqZYEbMd3em7/vEomDb2LAQKAy1RFfv41mdNw=="],
@@ -3271,7 +3274,7 @@
"terracotta": ["terracotta@1.0.6", "", { "dependencies": { "solid-use": "^0.9.0" }, "peerDependencies": { "solid-js": "^1.8" } }, "sha512-yVrmT/Lg6a3tEbeYEJH8ksb1PYkR5FA9k5gr1TchaSNIiA2ZWs5a+koEbePXwlBP0poaV7xViZ/v50bQFcMgqw=="],
"terser": ["terser@5.44.0", "", { "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-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w=="],
"terser": ["terser@5.44.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-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw=="],
"text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="],
@@ -3305,8 +3308,6 @@
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
"tree-sitter": ["tree-sitter@0.25.0", "", { "dependencies": { "node-addon-api": "^8.3.0", "node-gyp-build": "^4.8.4" } }, "sha512-PGZZzFW63eElZJDe/b/R/LbsjDDYJa5UEjLZJB59RQsMX+fo0j54fqBPn1MGKav/QNa0JR0zBiVaikYDWCj5KQ=="],
"tree-sitter-bash": ["tree-sitter-bash@0.25.0", "", { "dependencies": { "node-addon-api": "^8.2.1", "node-gyp-build": "^4.8.2" }, "peerDependencies": { "tree-sitter": "^0.25.0" }, "optionalPeers": ["tree-sitter"] }, "sha512-gZtlj9+qFS81qKxpLfD6H0UssQ3QBc/F0nKkPsiFDyfQF2YBqYvglFJUzchrPpVhZe9kLZTrJ9n2J6lmka69Vg=="],
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
@@ -3421,7 +3422,7 @@
"unplugin-utils": ["unplugin-utils@0.3.1", "", { "dependencies": { "pathe": "^2.0.3", "picomatch": "^4.0.3" } }, "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog=="],
"unstorage": ["unstorage@1.17.1", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", "destr": "^2.0.5", "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", "ofetch": "^1.4.1", "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-KKGwRTT0iVBCErKemkJCLs7JdxNVfqTPc/85ae1XES0+bsHbc/sFBfVi5kJp156cc51BHinIH2l3k0EZ24vOBQ=="],
"unstorage": ["unstorage@1.17.2", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^4.0.3", "destr": "^2.0.5", "h3": "^1.15.4", "lru-cache": "^10.4.3", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.0", "ufo": "^1.6.1" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6.0.3 || ^7.0.0", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1.0.1", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-cKEsD6iBWJgOMJ6vW1ID/SYuqNf8oN4yqRk8OYqaVQ3nnkJXOT1PSpaMh2QfzLs78UN5kSNRD2c/mgjT8tX7+w=="],
"untun": ["untun@0.1.3", "", { "dependencies": { "citty": "^0.1.5", "consola": "^3.2.3", "pathe": "^1.1.1" }, "bin": { "untun": "bin/untun.mjs" } }, "sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ=="],
@@ -3507,7 +3508,7 @@
"workerd": ["workerd@1.20251011.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20251011.0", "@cloudflare/workerd-darwin-arm64": "1.20251011.0", "@cloudflare/workerd-linux-64": "1.20251011.0", "@cloudflare/workerd-linux-arm64": "1.20251011.0", "@cloudflare/workerd-windows-64": "1.20251011.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-Dq35TLPEJAw7BuYQMkN3p9rge34zWMU2Gnd4DSJFeVqld4+DAO2aPG7+We2dNIAyM97S8Y9BmHulbQ00E0HC7Q=="],
"wrangler": ["wrangler@4.45.3", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.8", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251011.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.21", "workerd": "1.20251011.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251011.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-0ddEA9t4HeBgSVTVTcqtBHl7Z5CorWZ8tGgTQCP5XuL+9E1TJRwS6t/zzG51Ruwjb17SZYCaLchoM8V629S8cw=="],
"wrangler": ["wrangler@4.45.4", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.9", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251011.2", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20251011.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251011.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-niXT7B463wQi7WXIHjYK8txgWhuKQLrGmhjoR58SnPhlkq4wGjd3rFrkVyRc/O58clGTfs672BSGOph4XMoQKw=="],
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
@@ -3545,7 +3546,7 @@
"yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
"youch": ["youch@4.1.0-beta.11", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-sQi6PERyO/mT8w564ojOVeAlYTtVQmC2GaktQAf+IdI75/GKIggosBuvyVXvEV+FATAT6RbLdIjFoiIId4ozoQ=="],
"youch": ["youch@4.1.0-beta.12", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.5", "@speed-highlight/core": "^1.2.9", "cookie-es": "^2.0.0", "youch-core": "^0.3.3" } }, "sha512-X+AQ2EdigcZb2h1XQmBMm19TrrfKXxEXWpnf8ThbARwiiSf/pA7MvRTCj5VHCI9z3vjJBsDeqWWyvaI9Bfp9Pg=="],
"youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
@@ -3589,6 +3590,8 @@
"@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.6.1", "", {}, "sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A=="],
"@astrojs/markdown-remark/shiki": ["shiki@3.14.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/engine-javascript": "3.14.0", "@shikijs/engine-oniguruma": "3.14.0", "@shikijs/langs": "3.14.0", "@shikijs/themes": "3.14.0", "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g=="],
"@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.8", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.4", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.13.0", "smol-toml": "^1.4.2", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-uFNyFWadnULWK2cOw4n0hLKeu+xaVWeuECdP10cQ3K2fkybtTlhb7J7TcScdjmS8Yps7oje9S/ehYMfZrhrgCg=="],
"@astrojs/sitemap/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
@@ -3609,13 +3612,13 @@
"@cloudflare/kv-asset-handler/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
"@cloudflare/unenv-preset/unenv": ["unenv@2.0.0-rc.21", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.7", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A=="],
"@cloudflare/unenv-preset/unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
"@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
"@expressive-code/plugin-shiki/shiki": ["shiki@3.13.0", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/engine-javascript": "3.13.0", "@shikijs/engine-oniguruma": "3.13.0", "@shikijs/langs": "3.13.0", "@shikijs/themes": "3.13.0", "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g=="],
"@expressive-code/plugin-shiki/shiki": ["shiki@3.14.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/engine-javascript": "3.14.0", "@shikijs/engine-oniguruma": "3.14.0", "@shikijs/langs": "3.14.0", "@shikijs/themes": "3.14.0", "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g=="],
"@hono/zod-validator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
@@ -3725,11 +3728,11 @@
"@parcel/watcher-wasm/napi-wasm": ["napi-wasm@1.1.3", "", { "bundled": true }, "sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg=="],
"@pierre/precision-diffs/@shikijs/core": ["@shikijs/core@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA=="],
"@pierre/precision-diffs/@shikijs/core": ["@shikijs/core@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw=="],
"@pierre/precision-diffs/@shikijs/transformers": ["@shikijs/transformers@3.13.0", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/types": "3.13.0" } }, "sha512-833lcuVzcRiG+fXvgslWsM2f4gHpjEgui1ipIknSizRuTgMkNZupiXE5/TVJ6eSYfhNBFhBZKkReKWO2GgYmqA=="],
"@pierre/precision-diffs/@shikijs/transformers": ["@shikijs/transformers@3.14.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/types": "3.14.0" } }, "sha512-i67zQnY9wLMMnKasonVW1L9fKneSLZDj1ePsA4o0AZWU4uUobmJY9baRDa36z+a9/g0aG76/2tybQvm4hrwxIQ=="],
"@pierre/precision-diffs/shiki": ["shiki@3.13.0", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/engine-javascript": "3.13.0", "@shikijs/engine-oniguruma": "3.13.0", "@shikijs/langs": "3.13.0", "@shikijs/themes": "3.13.0", "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g=="],
"@pierre/precision-diffs/shiki": ["shiki@3.14.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/engine-javascript": "3.14.0", "@shikijs/engine-oniguruma": "3.14.0", "@shikijs/langs": "3.14.0", "@shikijs/themes": "3.14.0", "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g=="],
"@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
@@ -3757,9 +3760,9 @@
"@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.6.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.6.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
@@ -3775,6 +3778,8 @@
"@tanstack/server-functions-plugin/@babel/code-frame": ["@babel/code-frame@7.26.2", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ=="],
"@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
"@vercel/nft/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"@vercel/nft/glob": ["glob@10.4.5", "", { "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-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
@@ -3801,10 +3806,10 @@
"astro/diff": ["diff@5.2.0", "", {}, "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A=="],
"astro/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"astro/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=="],
"astro/shiki": ["shiki@3.14.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/engine-javascript": "3.14.0", "@shikijs/engine-oniguruma": "3.14.0", "@shikijs/langs": "3.14.0", "@shikijs/themes": "3.14.0", "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g=="],
"astro/vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
"astro/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
@@ -3849,7 +3854,7 @@
"dir-glob/path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
"dot-prop/type-fest": ["type-fest@5.1.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-wQ531tuWvB6oK+pchHIu5lHe5f5wpSCqB8Kf4dWQRbOYc9HTge7JL0G4Qd44bh6QuJCccIzL3bugb8GI0MwHrg=="],
"dot-prop/type-fest": ["type-fest@5.2.0", "", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-xxCJm+Bckc6kQBknN7i9fnP/xobQRsRQxR01CztFkp/h++yfVxUUcmMgfR2HttJx/dpWjS9ubVuyspJv24Q9DA=="],
"drizzle-kit/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=="],
@@ -3885,8 +3890,6 @@
"gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
"gel/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"giget/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
"giget/tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
@@ -4029,9 +4032,7 @@
"send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
"sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"sitemap/sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="],
"sitemap/sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="],
"source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
@@ -4047,8 +4048,6 @@
"strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
"style-to-js/style-to-object": ["style-to-object@1.0.11", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-5A560JmXr7wDyGLK12Nq/EYS38VkGlglVzkis1JEdbGWSnbQIEhZzTJhzURXN5/8WwwFCs/f/VVcmkTppbXLow=="],
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
"sucrase/glob": ["glob@10.4.5", "", { "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-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
@@ -4059,8 +4058,6 @@
"token-types/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"tree-sitter/node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="],
"tree-sitter-bash/node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="],
"tw-to-css/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
@@ -4097,7 +4094,7 @@
"wrangler/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],
"wrangler/unenv": ["unenv@2.0.0-rc.21", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.7", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A=="],
"wrangler/unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
@@ -4133,11 +4130,23 @@
"@actions/github/@octokit/request-error/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="],
"@astrojs/markdown-remark/shiki/@shikijs/core": ["@shikijs/core@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw=="],
"@astrojs/markdown-remark/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ=="],
"@astrojs/markdown-remark/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug=="],
"@astrojs/markdown-remark/shiki/@shikijs/langs": ["@shikijs/langs@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg=="],
"@astrojs/markdown-remark/shiki/@shikijs/themes": ["@shikijs/themes@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA=="],
"@astrojs/markdown-remark/shiki/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
"@astrojs/mdx/@astrojs/markdown-remark/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.4", "", {}, "sha512-lDA9MqE8WGi7T/t2BMi+EAXhs4Vcvr94Gqx3q15cFEz8oFZMO4/SFBqYr/UcmNlvW+35alowkVj+w9VhLvs5Cw=="],
"@astrojs/mdx/@astrojs/markdown-remark/@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="],
"@astrojs/mdx/@astrojs/markdown-remark/shiki": ["shiki@3.13.0", "", { "dependencies": { "@shikijs/core": "3.13.0", "@shikijs/engine-javascript": "3.13.0", "@shikijs/engine-oniguruma": "3.13.0", "@shikijs/langs": "3.13.0", "@shikijs/themes": "3.13.0", "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g=="],
"@astrojs/mdx/@astrojs/markdown-remark/shiki": ["shiki@3.14.0", "", { "dependencies": { "@shikijs/core": "3.14.0", "@shikijs/engine-javascript": "3.14.0", "@shikijs/engine-oniguruma": "3.14.0", "@shikijs/langs": "3.14.0", "@shikijs/themes": "3.14.0", "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
@@ -4191,17 +4200,17 @@
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
"@expressive-code/plugin-shiki/shiki/@shikijs/core": ["@shikijs/core@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA=="],
"@expressive-code/plugin-shiki/shiki/@shikijs/core": ["@shikijs/core@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw=="],
"@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg=="],
"@expressive-code/plugin-shiki/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ=="],
"@expressive-code/plugin-shiki/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg=="],
"@expressive-code/plugin-shiki/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug=="],
"@expressive-code/plugin-shiki/shiki/@shikijs/langs": ["@shikijs/langs@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ=="],
"@expressive-code/plugin-shiki/shiki/@shikijs/langs": ["@shikijs/langs@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg=="],
"@expressive-code/plugin-shiki/shiki/@shikijs/themes": ["@shikijs/themes@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg=="],
"@expressive-code/plugin-shiki/shiki/@shikijs/themes": ["@shikijs/themes@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA=="],
"@expressive-code/plugin-shiki/shiki/@shikijs/types": ["@shikijs/types@3.13.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="],
"@expressive-code/plugin-shiki/shiki/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
@@ -4327,19 +4336,19 @@
"@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@pierre/precision-diffs/@shikijs/core/@shikijs/types": ["@shikijs/types@3.13.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="],
"@pierre/precision-diffs/@shikijs/core/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
"@pierre/precision-diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.13.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="],
"@pierre/precision-diffs/@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
"@pierre/precision-diffs/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg=="],
"@pierre/precision-diffs/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ=="],
"@pierre/precision-diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg=="],
"@pierre/precision-diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug=="],
"@pierre/precision-diffs/shiki/@shikijs/langs": ["@shikijs/langs@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ=="],
"@pierre/precision-diffs/shiki/@shikijs/langs": ["@shikijs/langs@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg=="],
"@pierre/precision-diffs/shiki/@shikijs/themes": ["@shikijs/themes@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg=="],
"@pierre/precision-diffs/shiki/@shikijs/themes": ["@shikijs/themes@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA=="],
"@pierre/precision-diffs/shiki/@shikijs/types": ["@shikijs/types@3.13.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="],
"@pierre/precision-diffs/shiki/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
"@slack/web-api/p-queue/eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
@@ -4379,6 +4388,20 @@
"archiver-utils/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=="],
"astro/sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"astro/shiki/@shikijs/core": ["@shikijs/core@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw=="],
"astro/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ=="],
"astro/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug=="],
"astro/shiki/@shikijs/langs": ["@shikijs/langs@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg=="],
"astro/shiki/@shikijs/themes": ["@shikijs/themes@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA=="],
"astro/shiki/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
"axios/form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"babel-plugin-module-resolver/glob/minimatch": ["minimatch@8.0.4", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA=="],
@@ -4387,6 +4410,8 @@
"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=="],
"bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"c12/pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
@@ -4499,7 +4524,7 @@
"opencontrol/@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
"parse-bmfont-xml/xml2js/sax": ["sax@1.4.1", "", {}, "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="],
"parse-bmfont-xml/xml2js/sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="],
"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=="],
@@ -4523,8 +4548,6 @@
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"style-to-js/style-to-object/inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="],
"sucrase/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"sucrase/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
@@ -4607,17 +4630,17 @@
"@actions/github/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
"@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/core": ["@shikijs/core@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA=="],
"@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/core": ["@shikijs/core@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw=="],
"@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg=="],
"@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ=="],
"@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg=="],
"@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug=="],
"@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/langs": ["@shikijs/langs@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ=="],
"@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/langs": ["@shikijs/langs@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg=="],
"@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/themes": ["@shikijs/themes@3.13.0", "", { "dependencies": { "@shikijs/types": "3.13.0" } }, "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg=="],
"@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/themes": ["@shikijs/themes@3.14.0", "", { "dependencies": { "@shikijs/types": "3.14.0" } }, "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA=="],
"@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/types": ["@shikijs/types@3.13.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw=="],
"@astrojs/mdx/@astrojs/markdown-remark/shiki/@shikijs/types": ["@shikijs/types@3.14.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ=="],
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
@@ -4749,7 +4772,7 @@
"@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=="],
"nitropack/c12/giget/nypm/tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="],
"nitropack/c12/giget/nypm/tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
"opencontrol/@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
+1 -1
View File
@@ -6,4 +6,4 @@
/// <reference path="../sst-env.d.ts" />
import "sst"
export {}
export {}
@@ -1,15 +1,15 @@
{
"keep": {
"days": true,
"amount": 14
},
"auditLog": "/home/thdxr/dev/projects/sst/opencode/logs/.2c5480b3b2480f80fa29b850af461dce619c0b2f-audit.json",
"files": [
{
"date": 1759827172859,
"name": "/home/thdxr/dev/projects/sst/opencode/logs/mcp-puppeteer-2025-10-07.log",
"hash": "a3d98b26edd793411b968a0d24cfeee8332138e282023c3b83ec169d55c67f16"
}
],
"hashType": "sha256"
}
"keep": {
"days": true,
"amount": 14
},
"auditLog": "/home/thdxr/dev/projects/sst/opencode/logs/.2c5480b3b2480f80fa29b850af461dce619c0b2f-audit.json",
"files": [
{
"date": 1759827172859,
"name": "/home/thdxr/dev/projects/sst/opencode/logs/mcp-puppeteer-2025-10-07.log",
"hash": "a3d98b26edd793411b968a0d24cfeee8332138e282023c3b83ec169d55c67f16"
}
],
"hashType": "sha256"
}
+2 -2
View File
@@ -28,7 +28,7 @@
"@tsconfig/bun": "1.0.9",
"@cloudflare/workers-types": "4.20251008.0",
"@openauthjs/openauth": "0.0.0-20250322224806",
"@pierre/precision-diffs": "0.4.1",
"@pierre/precision-diffs": "0.4.4",
"@solidjs/meta": "0.29.4",
"@tailwindcss/vite": "4.1.11",
"diff": "8.0.2",
@@ -52,7 +52,7 @@
"@tsconfig/bun": "catalog:",
"husky": "9.1.7",
"prettier": "3.6.2",
"sst": "3.17.19",
"sst": "3.17.22",
"turbo": "2.5.6"
},
"dependencies": {
+1 -1
View File
@@ -7,7 +7,7 @@
"dev:remote": "VITE_AUTH_URL=https://auth.dev.opencode.ai bun sst shell --stage=dev bun dev",
"build": "vinxi build && ../../opencode/script/schema.ts ./.output/public/config.json",
"start": "vinxi start",
"version": "1.0.23"
"version": "1.0.39"
},
"dependencies": {
"@ibm/plex": "6.4.1",
+4 -1
View File
@@ -12,7 +12,10 @@ export default function App() {
root={(props) => (
<MetaProvider>
<Title>opencode</Title>
<Meta name="description" content="opencode - The AI coding agent built for the terminal." />
<Meta
name="description"
content="OpenCode - The AI coding agent built for the terminal."
/>
<Suspense>{props.children}</Suspense>
</MetaProvider>
)}
@@ -77,4 +77,4 @@
background-color: var(--color-accent-alpha);
}
}
}
}
+4 -1
View File
@@ -13,7 +13,10 @@ export function Faq(props: ParentProps & { question: string }) {
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12.5 11.5H19V12.5H12.5V19H11.5V12.5H5V11.5H11.5V5H12.5V11.5Z" fill="currentColor" />
<path
d="M12.5 11.5H19V12.5H12.5V19H11.5V12.5H5V11.5H11.5V5H12.5V11.5Z"
fill="currentColor"
/>
</svg>
<svg
data-slot="faq-icon-minus"
+113 -20
View File
@@ -9,10 +9,23 @@ export function IconLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
<path d="M13.7124 9.14333V4.5719H18.2838V9.14333H13.7124Z" fill="currentColor" />
<path d="M13.7124 13.7136V9.14221H18.2838V13.7136H13.7124Z" fill="currentColor" />
<path d="M0 18.2857V13.7142H4.57143V18.2857H0Z" fill="currentColor" fill-opacity="0.2" />
<rect width="4.57143" height="4.57143" transform="translate(4.57178 13.7141)" fill="currentColor" />
<path d="M4.57178 18.2855V13.7141H9.14321V18.2855H4.57178Z" fill="currentColor" fill-opacity="0.2" />
<rect
width="4.57143"
height="4.57143"
transform="translate(4.57178 13.7141)"
fill="currentColor"
/>
<path
d="M4.57178 18.2855V13.7141H9.14321V18.2855H4.57178Z"
fill="currentColor"
fill-opacity="0.2"
/>
<path d="M9.1438 18.2855V13.7141H13.7152V18.2855H9.1438Z" fill="currentColor" />
<path d="M13.7156 18.2855V13.7141H18.287V18.2855H13.7156Z" fill="currentColor" fill-opacity="0.2" />
<path
d="M13.7156 18.2855V13.7141H18.287V18.2855H13.7156Z"
fill="currentColor"
fill-opacity="0.2"
/>
<rect width="4.57143" height="4.57143" transform="translate(0 18.2859)" fill="currentColor" />
<path d="M0 22.8572V18.2858H4.57143V22.8572H0Z" fill="currentColor" fill-opacity="0.2" />
<rect
@@ -23,8 +36,16 @@ export function IconLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
fill-opacity="0.2"
/>
<path d="M4.57178 22.8573V18.2859H9.14321V22.8573H4.57178Z" fill="currentColor" />
<path d="M9.1438 22.8573V18.2859H13.7152V22.8573H9.1438Z" fill="currentColor" fill-opacity="0.2" />
<path d="M13.7156 22.8573V18.2859H18.287V22.8573H13.7156Z" fill="currentColor" fill-opacity="0.2" />
<path
d="M9.1438 22.8573V18.2859H13.7152V22.8573H9.1438Z"
fill="currentColor"
fill-opacity="0.2"
/>
<path
d="M13.7156 22.8573V18.2859H18.287V22.8573H13.7156Z"
fill="currentColor"
fill-opacity="0.2"
/>
<path d="M0 27.4292V22.8578H4.57143V27.4292H0Z" fill="currentColor" />
<path d="M4.57178 27.4292V22.8578H9.14321V27.4292H4.57178Z" fill="currentColor" />
<path d="M9.1438 27.4276V22.8562H13.7152V27.4276H9.1438Z" fill="currentColor" />
@@ -40,9 +61,21 @@ export function IconLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
<path d="M32.001 18.2855V13.7141H36.5724V18.2855H32.001Z" fill="currentColor" />
<path d="M36.5698 18.2855V13.7141H41.1413V18.2855H36.5698Z" fill="currentColor" />
<path d="M22.8572 22.8573V18.2859H27.4286V22.8573H22.8572Z" fill="currentColor" />
<path d="M27.4292 22.8573V18.2859H32.0006V22.8573H27.4292Z" fill="currentColor" fill-opacity="0.2" />
<path d="M32.001 22.8573V18.2859H36.5724V22.8573H32.001Z" fill="currentColor" fill-opacity="0.2" />
<path d="M36.5698 22.8573V18.2859H41.1413V22.8573H36.5698Z" fill="currentColor" fill-opacity="0.2" />
<path
d="M27.4292 22.8573V18.2859H32.0006V22.8573H27.4292Z"
fill="currentColor"
fill-opacity="0.2"
/>
<path
d="M32.001 22.8573V18.2859H36.5724V22.8573H32.001Z"
fill="currentColor"
fill-opacity="0.2"
/>
<path
d="M36.5698 22.8573V18.2859H41.1413V22.8573H36.5698Z"
fill="currentColor"
fill-opacity="0.2"
/>
<path d="M22.8572 27.4292V22.8578H27.4286V27.4292H22.8572Z" fill="currentColor" />
<path d="M27.4292 27.4276V22.8562H32.0006V27.4276H27.4292Z" fill="currentColor" />
<path d="M32.001 27.4276V22.8562H36.5724V27.4276H32.001Z" fill="currentColor" />
@@ -53,16 +86,40 @@ export function IconLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
<path d="M45.7144 13.7136V9.14221H50.2858V13.7136H45.7144Z" fill="currentColor" />
<path d="M59.4299 13.7152V9.1438H64.0014V13.7152H59.4299Z" fill="currentColor" />
<path d="M45.7144 18.2855V13.7141H50.2858V18.2855H45.7144Z" fill="currentColor" />
<path d="M50.2861 18.2857V13.7142H54.8576V18.2857H50.2861Z" fill="currentColor" fill-opacity="0.2" />
<path d="M54.8579 18.2855V13.7141H59.4293V18.2855H54.8579Z" fill="currentColor" fill-opacity="0.2" />
<path
d="M50.2861 18.2857V13.7142H54.8576V18.2857H50.2861Z"
fill="currentColor"
fill-opacity="0.2"
/>
<path
d="M54.8579 18.2855V13.7141H59.4293V18.2855H54.8579Z"
fill="currentColor"
fill-opacity="0.2"
/>
<path d="M59.4299 18.2855V13.7141H64.0014V18.2855H59.4299Z" fill="currentColor" />
<path d="M45.7144 22.8573V18.2859H50.2858V22.8573H45.7144Z" fill="currentColor" />
<path d="M50.2861 22.8572V18.2858H54.8576V22.8572H50.2861Z" fill="currentColor" fill-opacity="0.2" />
<path d="M54.8579 22.8573V18.2859H59.4293V22.8573H54.8579Z" fill="currentColor" fill-opacity="0.2" />
<path
d="M50.2861 22.8572V18.2858H54.8576V22.8572H50.2861Z"
fill="currentColor"
fill-opacity="0.2"
/>
<path
d="M54.8579 22.8573V18.2859H59.4293V22.8573H54.8579Z"
fill="currentColor"
fill-opacity="0.2"
/>
<path d="M59.4299 22.8573V18.2859H64.0014V22.8573H59.4299Z" fill="currentColor" />
<path d="M45.7144 27.4292V22.8578H50.2858V27.4292H45.7144Z" fill="currentColor" />
<path d="M50.2861 27.4286V22.8572H54.8576V27.4286H50.2861Z" fill="currentColor" fill-opacity="0.2" />
<path d="M54.8579 27.4285V22.8571H59.4293V27.4285H54.8579Z" fill="currentColor" fill-opacity="0.2" />
<path
d="M50.2861 27.4286V22.8572H54.8576V27.4286H50.2861Z"
fill="currentColor"
fill-opacity="0.2"
/>
<path
d="M54.8579 27.4285V22.8571H59.4293V27.4285H54.8579Z"
fill="currentColor"
fill-opacity="0.2"
/>
<path d="M59.4299 27.4292V22.8578H64.0014V27.4292H59.4299Z" fill="currentColor" />
</svg>
)
@@ -70,7 +127,14 @@ export function IconLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
export function IconCopy(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
{...props}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.75 8.75V2.75H21.25V15.25H15.25M15.25 8.75H2.75V21.25H15.25V8.75Z"
stroke="currentColor"
@@ -83,8 +147,20 @@ export function IconCopy(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
export function IconCheck(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.75 15.0938L9 20.25L21.25 3.75" stroke="#03B000" stroke-width="2" stroke-linecap="square" />
<svg
{...props}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.75 15.0938L9 20.25L21.25 3.75"
stroke="#03B000"
stroke-width="2"
stroke-linecap="square"
/>
</svg>
)
}
@@ -113,7 +189,14 @@ export function IconStripe(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
export function IconChevron(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} width="8" height="6" viewBox="0 0 8 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
{...props}
width="8"
height="6"
viewBox="0 0 8 6"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="currentColor"
d="M4.00024 5.04041L7.37401 1.66663L6.66691 0.959525L4.00024 3.62619L1.33357 0.959525L0.626465 1.66663L4.00024 5.04041Z"
@@ -124,7 +207,14 @@ export function IconChevron(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
export function IconWorkspaceLogo(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} width="24" height="30" viewBox="0 0 24 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
{...props}
width="24"
height="30"
viewBox="0 0 24 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M18 6H6V24H18V6ZM24 30H0V0H24V30Z" fill="currentColor" />
</svg>
)
@@ -144,7 +234,10 @@ export function IconOpenAI(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
export function IconAnthropic(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M13.7891 3.93188L20.2223 20.068H23.7502L17.317 3.93188H13.7891Z" />
<path
fill="currentColor"
d="M13.7891 3.93188L20.2223 20.068H23.7502L17.317 3.93188H13.7891Z"
/>
<path
fill="currentColor"
d="M6.32538 13.6827L8.52662 8.01201L10.7279 13.6827H6.32538ZM6.68225 3.93188L0.25 20.068H3.84652L5.16202 16.6794H11.8914L13.2067 20.068H16.8033L10.371 3.93188H6.68225Z"
+1 -1
View File
@@ -63,4 +63,4 @@
font-weight: 600;
color: var(--color-text);
}
}
}
@@ -2,6 +2,9 @@ import type { APIEvent } from "@solidjs/start/server"
import { AuthClient } from "~/context/auth"
export async function GET(input: APIEvent) {
const result = await AuthClient.authorize(new URL("./callback", input.request.url).toString(), "code")
const result = await AuthClient.authorize(
new URL("./callback", input.request.url).toString(),
"code",
)
return Response.redirect(result.url, 302)
}
@@ -299,7 +299,6 @@
transition: all 0.2s ease;
text-decoration: none;
&:hover:not(:disabled) {
background: var(--color-background-strong-hover);
}
@@ -385,23 +384,21 @@
0 1px 2px -1px rgba(19, 16, 16, 0.12);
@media (max-width: 40rem) {
box-shadow:
0 0 0 1px rgba(19, 16, 16, 0.16)
box-shadow: 0 0 0 1px rgba(19, 16, 16, 0.16);
}
&:hover {
background: var(--color-background);
}
&:active {
transform: scale(0.98);
box-shadow: 0 0 0 1px rgba(19, 16, 16, 0.08), 0 6px 8px -8px rgba(19, 16, 16, 0.50);
box-shadow:
0 0 0 1px rgba(19, 16, 16, 0.08),
0 6px 8px -8px rgba(19, 16, 16, 0.5);
}
}
@media (max-width: 60rem) {
padding: 2rem 1.5rem;
}
@@ -0,0 +1,5 @@
import { redirect } from "@solidjs/router"
export async function GET() {
return redirect("https://discord.gg/h5TNnkFVNy")
}
+138 -127
View File
@@ -13,146 +13,157 @@ export async function POST(input: APIEvent) {
input.request.headers.get("stripe-signature")!,
Resource.STRIPE_WEBHOOK_SECRET.value,
)
console.log(body.type, JSON.stringify(body, null, 2))
if (body.type === "customer.updated") {
// check default payment method changed
const prevInvoiceSettings = body.data.previous_attributes?.invoice_settings ?? {}
if (!("default_payment_method" in prevInvoiceSettings)) return
const customerID = body.data.object.id
const paymentMethodID = body.data.object.invoice_settings.default_payment_method as string
return (async () => {
if (body.type === "customer.updated") {
// check default payment method changed
const prevInvoiceSettings = body.data.previous_attributes?.invoice_settings ?? {}
if (!("default_payment_method" in prevInvoiceSettings)) return "ignored"
if (!customerID) throw new Error("Customer ID not found")
if (!paymentMethodID) throw new Error("Payment method ID not found")
const customerID = body.data.object.id
const paymentMethodID = body.data.object.invoice_settings.default_payment_method as string
const paymentMethod = await Billing.stripe().paymentMethods.retrieve(paymentMethodID)
await Database.use(async (tx) => {
await tx
.update(BillingTable)
.set({
paymentMethodID,
paymentMethodLast4: paymentMethod.card?.last4 ?? null,
paymentMethodType: paymentMethod.type,
})
.where(eq(BillingTable.customerID, customerID))
})
}
if (body.type === "checkout.session.completed") {
const workspaceID = body.data.object.metadata?.workspaceID
const customerID = body.data.object.customer as string
const paymentID = body.data.object.payment_intent as string
const invoiceID = body.data.object.invoice as string
const amount = body.data.object.amount_total
if (!customerID) throw new Error("Customer ID not found")
if (!paymentMethodID) throw new Error("Payment method ID not found")
if (!workspaceID) throw new Error("Workspace ID not found")
if (!customerID) throw new Error("Customer ID not found")
if (!amount) throw new Error("Amount not found")
if (!paymentID) throw new Error("Payment ID not found")
if (!invoiceID) throw new Error("Invoice ID not found")
await Actor.provide("system", { workspaceID }, async () => {
const customer = await Billing.get()
if (customer?.customerID && customer.customerID !== customerID)
throw new Error("Customer ID mismatch")
// set customer metadata
if (!customer?.customerID) {
await Billing.stripe().customers.update(customerID, {
metadata: {
workspaceID,
},
})
}
// get payment method for the payment intent
const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
expand: ["payment_method"],
})
const paymentMethod = paymentIntent.payment_method
if (!paymentMethod || typeof paymentMethod === "string")
throw new Error("Payment method not expanded")
const oldBillingInfo = await Database.use((tx) =>
tx
.select({
customerID: BillingTable.customerID,
})
.from(BillingTable)
.where(eq(BillingTable.workspaceID, workspaceID))
.then((rows) => rows[0]),
)
await Database.transaction(async (tx) => {
const paymentMethod = await Billing.stripe().paymentMethods.retrieve(paymentMethodID)
await Database.use(async (tx) => {
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} + ${centsToMicroCents(Billing.CHARGE_AMOUNT)}`,
customerID,
paymentMethodID: paymentMethod.id,
paymentMethodID,
paymentMethodLast4: paymentMethod.card?.last4 ?? null,
paymentMethodType: paymentMethod.type,
// enable reload if first time enabling billing
...(oldBillingInfo?.customerID
? {}
: {
reload: true,
reloadError: null,
timeReloadError: null,
}),
})
.where(eq(BillingTable.workspaceID, workspaceID))
await tx.insert(PaymentTable).values({
workspaceID,
id: Identifier.create("payment"),
amount: centsToMicroCents(Billing.CHARGE_AMOUNT),
paymentID,
invoiceID,
customerID,
.where(eq(BillingTable.customerID, customerID))
})
}
if (body.type === "checkout.session.completed") {
const workspaceID = body.data.object.metadata?.workspaceID
const amountInCents =
body.data.object.metadata?.amount && parseInt(body.data.object.metadata?.amount)
const customerID = body.data.object.customer as string
const paymentID = body.data.object.payment_intent as string
const invoiceID = body.data.object.invoice as string
if (!workspaceID) throw new Error("Workspace ID not found")
if (!customerID) throw new Error("Customer ID not found")
if (!amountInCents) throw new Error("Amount not found")
if (!paymentID) throw new Error("Payment ID not found")
if (!invoiceID) throw new Error("Invoice ID not found")
await Actor.provide("system", { workspaceID }, async () => {
const customer = await Billing.get()
if (customer?.customerID && customer.customerID !== customerID)
throw new Error("Customer ID mismatch")
// set customer metadata
if (!customer?.customerID) {
await Billing.stripe().customers.update(customerID, {
metadata: {
workspaceID,
},
})
}
// get payment method for the payment intent
const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
expand: ["payment_method"],
})
const paymentMethod = paymentIntent.payment_method
if (!paymentMethod || typeof paymentMethod === "string")
throw new Error("Payment method not expanded")
await Database.transaction(async (tx) => {
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} + ${centsToMicroCents(amountInCents)}`,
customerID,
paymentMethodID: paymentMethod.id,
paymentMethodLast4: paymentMethod.card?.last4 ?? null,
paymentMethodType: paymentMethod.type,
// enable reload if first time enabling billing
...(customer?.customerID
? {}
: {
reload: true,
reloadError: null,
timeReloadError: null,
}),
})
.where(eq(BillingTable.workspaceID, workspaceID))
await tx.insert(PaymentTable).values({
workspaceID,
id: Identifier.create("payment"),
amount: centsToMicroCents(amountInCents),
paymentID,
invoiceID,
customerID,
})
})
})
}
if (body.type === "charge.refunded") {
const customerID = body.data.object.customer as string
const paymentIntentID = body.data.object.payment_intent as string
if (!customerID) throw new Error("Customer ID not found")
if (!paymentIntentID) throw new Error("Payment ID not found")
const workspaceID = await Database.use((tx) =>
tx
.select({
workspaceID: BillingTable.workspaceID,
})
.from(BillingTable)
.where(eq(BillingTable.customerID, customerID))
.then((rows) => rows[0]?.workspaceID),
)
if (!workspaceID) throw new Error("Workspace ID not found")
const amount = await Database.use((tx) =>
tx
.select({
amount: PaymentTable.amount,
})
.from(PaymentTable)
.where(
and(
eq(PaymentTable.paymentID, paymentIntentID),
eq(PaymentTable.workspaceID, workspaceID),
),
)
.then((rows) => rows[0]?.amount),
)
if (!amount) throw new Error("Payment not found")
await Database.transaction(async (tx) => {
await tx
.update(PaymentTable)
.set({
timeRefunded: new Date(body.created * 1000),
})
.where(
and(
eq(PaymentTable.paymentID, paymentIntentID),
eq(PaymentTable.workspaceID, workspaceID),
),
)
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} - ${amount}`,
})
.where(eq(BillingTable.workspaceID, workspaceID))
})
}
})()
.then((message) => {
return Response.json({ message: message ?? "done" }, { status: 200 })
})
}
if (body.type === "charge.refunded") {
const customerID = body.data.object.customer as string
const paymentIntentID = body.data.object.payment_intent as string
if (!customerID) throw new Error("Customer ID not found")
if (!paymentIntentID) throw new Error("Payment ID not found")
const workspaceID = await Database.use((tx) =>
tx
.select({
workspaceID: BillingTable.workspaceID,
})
.from(BillingTable)
.where(eq(BillingTable.customerID, customerID))
.then((rows) => rows[0]?.workspaceID),
)
if (!workspaceID) throw new Error("Workspace ID not found")
await Database.transaction(async (tx) => {
await tx
.update(PaymentTable)
.set({
timeRefunded: new Date(body.created * 1000),
})
.where(
and(
eq(PaymentTable.paymentID, paymentIntentID),
eq(PaymentTable.workspaceID, workspaceID),
),
)
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} - ${centsToMicroCents(Billing.CHARGE_AMOUNT)}`,
})
.where(eq(BillingTable.workspaceID, workspaceID))
.catch((error: any) => {
return Response.json({ message: error.message }, { status: 500 })
})
}
console.log("finished handling")
return Response.json("ok", { status: 200 })
}
@@ -14,4 +14,4 @@
color: var(--color-danger);
}
}
}
}
@@ -71,4 +71,4 @@
color: var(--color-text-muted);
}
}
}
}
@@ -85,7 +85,10 @@ export function WorkspacePicker() {
<Dropdown trigger={currentWorkspace()} align="left">
<For each={workspaces()}>
{(workspace) => (
<DropdownItem selected={workspace.id === params.id} onClick={() => handleSelectWorkspace(workspace.id)}>
<DropdownItem
selected={workspace.id === params.id}
onClick={() => handleSelectWorkspace(workspace.id)}
>
{workspace.name || workspace.slug}
</DropdownItem>
)}
@@ -95,7 +98,11 @@ export function WorkspacePicker() {
</button>
</Dropdown>
<Modal open={store.showForm} onClose={() => setStore("showForm", false)} title="Create New Workspace">
<Modal
open={store.showForm}
onClose={() => setStore("showForm", false)}
title="Create New Workspace"
>
<form data-slot="create-form" action={createWorkspace} method="post">
<div data-slot="create-input-group">
<input
@@ -104,4 +104,4 @@
}
}
}
}
}
@@ -71,6 +71,57 @@
flex: 1;
}
[data-slot="add-balance-form-container"] {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
[data-slot="add-balance-form"] {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--space-3);
label {
font-size: var(--font-size-sm);
font-weight: 500;
color: var(--color-text-muted);
white-space: nowrap;
}
input[data-component="input"] {
padding: var(--space-2) var(--space-3);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg);
color: var(--color-text);
font-size: var(--font-size-sm);
line-height: 1.5;
&:focus {
outline: none;
border-color: var(--color-accent);
box-shadow: 0 0 0 3px var(--color-accent-alpha);
}
&::placeholder {
color: var(--color-text-disabled);
}
}
[data-slot="form-actions"] {
display: flex;
gap: var(--space-2);
}
}
[data-slot="form-error"] {
color: var(--color-danger);
font-size: var(--font-size-sm);
line-height: 1.4;
}
[data-slot="credit-card"] {
padding: var(--space-2) var(--space-4);
background-color: var(--color-bg-surface);
@@ -131,4 +182,4 @@
padding: var(--space-4);
min-width: 150px;
}
}
}
@@ -1,24 +1,80 @@
import { action, useParams, useAction, createAsync, useSubmission } from "@solidjs/router"
import { createMemo, Match, Show, Switch } from "solid-js"
import { action, useParams, useAction, createAsync, useSubmission, json } from "@solidjs/router"
import { createMemo, Match, Show, Switch, createEffect } from "solid-js"
import { createStore } from "solid-js/store"
import { Billing } from "@opencode-ai/console-core/billing.js"
import { withActor } from "~/context/auth.withActor"
import { IconCreditCard, IconStripe } from "~/component/icon"
import styles from "./billing-section.module.css"
import { createCheckoutUrl, queryBillingInfo } from "../../common"
import { createCheckoutUrl, formatBalance, queryBillingInfo } from "../../common"
const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => {
"use server"
return withActor(() => Billing.generateSessionUrl({ returnUrl }), workspaceID)
return json(
await withActor(
() =>
Billing.generateSessionUrl({ returnUrl })
.then((data) => ({ error: undefined, data }))
.catch((e) => ({
error: e.message as string,
data: undefined,
})),
workspaceID,
),
{ revalidate: queryBillingInfo.key },
)
}, "sessionUrl")
export function BillingSection() {
const params = useParams()
// ORIGINAL CODE - COMMENTED OUT FOR TESTING
const balanceInfo = createAsync(() => queryBillingInfo(params.id))
const createCheckoutUrlAction = useAction(createCheckoutUrl)
const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
const createSessionUrlAction = useAction(createSessionUrl)
const createSessionUrlSubmission = useSubmission(createSessionUrl)
const billingInfo = createAsync(() => queryBillingInfo(params.id))
const checkoutAction = useAction(createCheckoutUrl)
const checkoutSubmission = useSubmission(createCheckoutUrl)
const sessionAction = useAction(createSessionUrl)
const sessionSubmission = useSubmission(createSessionUrl)
const [store, setStore] = createStore({
showAddBalanceForm: false,
addBalanceAmount: "",
checkoutRedirecting: false,
sessionRedirecting: false,
})
const balance = createMemo(() => formatBalance(billingInfo()?.balance ?? 0))
async function onClickCheckout() {
const amount = parseInt(store.addBalanceAmount)
const baseUrl = window.location.href
const checkout = await checkoutAction(params.id, amount, baseUrl, baseUrl)
if (checkout && checkout.data) {
setStore("checkoutRedirecting", true)
window.location.href = checkout.data
}
}
async function onClickSession() {
const baseUrl = window.location.href
const sessionUrl = await sessionAction(params.id, baseUrl)
if (sessionUrl && sessionUrl.data) {
setStore("sessionRedirecting", true)
window.location.href = sessionUrl.data
}
}
function showAddBalanceForm() {
while (true) {
checkoutSubmission.clear()
if (!checkoutSubmission.result) break
}
setStore({
showAddBalanceForm: true,
addBalanceAmount: billingInfo()!.reloadAmount.toString(),
})
}
function hideAddBalanceForm() {
setStore("showAddBalanceForm", false)
checkoutSubmission.clear()
}
// DUMMY DATA FOR TESTING - UNCOMMENT ONE OF THE SCENARIOS BELOW
@@ -72,10 +128,6 @@ export function BillingSection() {
// timeReloadError: null as Date | null
// })
const balanceAmount = createMemo(() => {
return ((balanceInfo()?.balance ?? 0) / 100000000).toFixed(2)
})
return (
<section class={styles.root}>
<div data-slot="section-title">
@@ -88,81 +140,110 @@ export function BillingSection() {
<div data-slot="section-content">
<div data-slot="balance-display">
<div data-slot="balance-amount">
<span data-slot="balance-value">
${balanceAmount() === "-0.00" ? "0.00" : balanceAmount()}
</span>
<span data-slot="balance-value">${balance()}</span>
<span data-slot="balance-label">Current Balance</span>
</div>
<Show when={balanceInfo()?.customerID}>
<Show when={billingInfo()?.customerID}>
<div data-slot="balance-right-section">
<button
data-color="primary"
disabled={createCheckoutUrlSubmission.pending}
onClick={async () => {
const baseUrl = window.location.href
const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
if (checkoutUrl) {
window.location.href = checkoutUrl
}
}}
<Show
when={!store.showAddBalanceForm}
fallback={
<div data-slot="add-balance-form-container">
<div data-slot="add-balance-form">
<label>Add $</label>
<input
data-component="input"
type="number"
min={billingInfo()?.reloadAmountMin.toString()}
step="1"
value={store.addBalanceAmount}
onInput={(e) => {
setStore("addBalanceAmount", e.currentTarget.value)
checkoutSubmission.clear()
}}
placeholder="Enter amount"
/>
<div data-slot="form-actions">
<button
data-color="ghost"
type="button"
onClick={() => hideAddBalanceForm()}
>
Cancel
</button>
<button
data-color="primary"
type="button"
disabled={
!store.addBalanceAmount ||
checkoutSubmission.pending ||
store.checkoutRedirecting
}
onClick={onClickCheckout}
>
{checkoutSubmission.pending || store.checkoutRedirecting
? "Loading..."
: "Add"}
</button>
</div>
</div>
<Show
when={checkoutSubmission.result && (checkoutSubmission.result as any).error}
>
{(err: any) => <div data-slot="form-error">{err()}</div>}
</Show>
</div>
}
>
{createCheckoutUrlSubmission.pending ? "Loading..." : "Add Balance"}
</button>
<button data-color="primary" onClick={() => showAddBalanceForm()}>
Add Balance
</button>
</Show>
<div data-slot="credit-card">
<div data-slot="card-icon">
<Switch fallback={<IconCreditCard style={{ width: "24px", height: "24px" }} />}>
<Match when={balanceInfo()?.paymentMethodType === "link"}>
<Match when={billingInfo()?.paymentMethodType === "link"}>
<IconStripe style={{ width: "24px", height: "24px" }} />
</Match>
</Switch>
</div>
<div data-slot="card-details">
<Switch>
<Match when={balanceInfo()?.paymentMethodType === "card"}>
<Match when={billingInfo()?.paymentMethodType === "card"}>
<Show
when={balanceInfo()?.paymentMethodLast4}
when={billingInfo()?.paymentMethodLast4}
fallback={<span data-slot="number">----</span>}
>
<span data-slot="secret"></span>
<span data-slot="number">{balanceInfo()?.paymentMethodLast4}</span>
<span data-slot="number">{billingInfo()?.paymentMethodLast4}</span>
</Show>
</Match>
<Match when={balanceInfo()?.paymentMethodType === "link"}>
<Match when={billingInfo()?.paymentMethodType === "link"}>
<span data-slot="type">Linked to Stripe</span>
</Match>
</Switch>
</div>
<button
data-color="ghost"
disabled={createSessionUrlSubmission.pending}
onClick={async () => {
const baseUrl = window.location.href
const sessionUrl = await createSessionUrlAction(params.id, baseUrl)
if (sessionUrl) {
window.location.href = sessionUrl
}
}}
disabled={sessionSubmission.pending || store.sessionRedirecting}
onClick={onClickSession}
>
{createSessionUrlSubmission.pending ? "Loading..." : "Manage"}
{sessionSubmission.pending || store.sessionRedirecting ? "Loading..." : "Manage"}
</button>
</div>
</div>
</Show>
</div>
<Show when={!balanceInfo()?.customerID}>
<Show when={!billingInfo()?.customerID}>
<button
data-slot="enable-billing-button"
data-color="primary"
disabled={createCheckoutUrlSubmission.pending}
onClick={async () => {
const baseUrl = window.location.href
const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
if (checkoutUrl) {
window.location.href = checkoutUrl
}
}}
disabled={checkoutSubmission.pending || store.checkoutRedirecting}
onClick={onClickCheckout}
>
{createCheckoutUrlSubmission.pending ? "Loading..." : "Enable Billing"}
{checkoutSubmission.pending || store.checkoutRedirecting
? "Loading..."
: "Enable Billing"}
</button>
</Show>
</div>
@@ -93,4 +93,4 @@
margin: 0;
line-height: 1.4;
}
}
}
@@ -1,16 +1,10 @@
import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router"
import { json, action, useParams, createAsync, useSubmission } from "@solidjs/router"
import { createEffect, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { withActor } from "~/context/auth.withActor"
import { Billing } from "@opencode-ai/console-core/billing.js"
import styles from "./monthly-limit-section.module.css"
const getBillingInfo = query(async (workspaceID: string) => {
"use server"
return withActor(async () => {
return await Billing.get()
}, workspaceID)
}, "billing.get")
import { queryBillingInfo } from "../../common"
const setMonthlyLimit = action(async (form: FormData) => {
"use server"
@@ -28,7 +22,7 @@ const setMonthlyLimit = action(async (form: FormData) => {
.catch((e) => ({ error: e.message as string })),
workspaceID,
),
{ revalidate: getBillingInfo.key },
{ revalidate: queryBillingInfo.key },
)
}, "billing.setMonthlyLimit")
@@ -36,7 +30,7 @@ export function MonthlyLimitSection() {
const params = useParams()
const submission = useSubmission(setMonthlyLimit)
const [store, setStore] = createStore({ show: false })
const balanceInfo = createAsync(() => getBillingInfo(params.id))
const billingInfo = createAsync(() => queryBillingInfo(params.id))
let input: HTMLInputElement
@@ -68,13 +62,13 @@ export function MonthlyLimitSection() {
<section class={styles.root}>
<div data-slot="section-title">
<h2>Monthly Limit</h2>
<p>Set a monthly spending limit for your account.</p>
<p>Set a monthly usage limit for your account.</p>
</div>
<div data-slot="section-content">
<div data-slot="balance">
<div data-slot="amount">
{balanceInfo()?.monthlyLimit ? <span data-slot="currency">$</span> : null}
<span data-slot="value">{balanceInfo()?.monthlyLimit ?? "-"}</span>
{billingInfo()?.monthlyLimit ? <span data-slot="currency">$</span> : null}
<span data-slot="value">{billingInfo()?.monthlyLimit ?? "-"}</span>
</div>
<Show
when={!store.show}
@@ -106,15 +100,19 @@ export function MonthlyLimitSection() {
}
>
<button data-color="primary" onClick={() => show()}>
{balanceInfo()?.monthlyLimit ? "Edit Limit" : "Set Limit"}
{billingInfo()?.monthlyLimit ? "Edit Limit" : "Set Limit"}
</button>
</Show>
</div>
<Show when={balanceInfo()?.monthlyLimit} fallback={<p data-slot="usage-status">No spending limit set.</p>}>
<Show
when={billingInfo()?.monthlyLimit}
fallback={<p data-slot="usage-status">No usage limit set.</p>}
>
<p data-slot="usage-status">
Current usage for {new Date().toLocaleDateString("en-US", { month: "long", timeZone: "UTC" })} is $
Current usage for{" "}
{new Date().toLocaleDateString("en-US", { month: "long", timeZone: "UTC" })} is $
{(() => {
const dateLastUsed = balanceInfo()?.timeMonthlyUsageUpdated
const dateLastUsed = billingInfo()?.timeMonthlyUsageUpdated
if (!dateLastUsed) return "0"
const current = new Date().toLocaleDateString("en-US", {
@@ -128,7 +126,7 @@ export function MonthlyLimitSection() {
timeZone: "UTC",
})
if (current !== lastUsed) return "0"
return ((balanceInfo()?.monthlyUsage ?? 0) / 100000000).toFixed(2)
return ((billingInfo()?.monthlyUsage ?? 0) / 100000000).toFixed(2)
})()}
.
</p>
@@ -89,7 +89,10 @@ export function PaymentSection() {
<td data-slot="payment-receipt">
<button
onClick={async () => {
const receiptUrl = await downloadReceiptAction(params.id, payment.paymentID!)
const receiptUrl = await downloadReceiptAction(
params.id,
payment.paymentID!,
)
if (receiptUrl) {
window.open(receiptUrl, "_blank")
}
@@ -34,6 +34,206 @@
}
}
[data-slot="create-form"] {
display: flex;
flex-direction: column;
gap: var(--space-3);
padding: var(--space-4);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
margin-top: var(--space-4);
[data-slot="form-field"] {
display: flex;
flex-direction: column;
gap: var(--space-2);
label {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
[data-slot="field-label"] {
font-size: var(--font-size-sm);
font-weight: 500;
color: var(--color-text-muted);
}
[data-slot="toggle-container"] {
display: flex;
align-items: center;
}
input[data-component="input"] {
flex: 1;
padding: var(--space-2) var(--space-3);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg);
color: var(--color-text);
font-size: var(--font-size-sm);
font-family: var(--font-mono);
&:focus {
outline: none;
border-color: var(--color-accent);
}
&::placeholder {
color: var(--color-text-disabled);
}
}
}
[data-slot="input-row"] {
display: flex;
flex-direction: row;
gap: var(--space-3);
@media (max-width: 40rem) {
flex-direction: column;
gap: var(--space-2);
}
}
[data-slot="input-field"] {
display: flex;
flex-direction: column;
gap: var(--space-1);
flex: 1;
p {
line-height: 1.2;
margin: 0;
color: var(--color-text-muted);
font-size: var(--font-size-sm);
}
input[data-component="input"] {
flex: 1;
padding: var(--space-2) var(--space-3);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
background-color: var(--color-bg);
color: var(--color-text);
font-size: var(--font-size-sm);
line-height: 1.5;
min-width: 0;
&:focus {
outline: none;
border-color: var(--color-accent);
box-shadow: 0 0 0 3px var(--color-accent-alpha);
}
&::placeholder {
color: var(--color-text-disabled);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
background-color: var(--color-bg-surface);
}
}
[data-slot="field-with-connector"] {
display: flex;
align-items: center;
gap: var(--space-2);
[data-slot="field-connector"] {
font-size: var(--font-size-sm);
color: var(--color-text-muted);
white-space: nowrap;
}
input[data-component="input"] {
flex: 1;
min-width: 80px;
}
}
}
[data-slot="form-actions"] {
display: flex;
gap: var(--space-2);
margin-top: var(--space-1);
}
[data-slot="form-error"] {
color: var(--color-danger);
font-size: var(--font-size-sm);
line-height: 1.4;
margin-top: calc(var(--space-1) * -1);
}
[data-slot="model-toggle-label"] {
position: relative;
display: inline-block;
width: 2.5rem;
height: 1.5rem;
cursor: pointer;
input {
opacity: 0;
width: 0;
height: 0;
}
span {
position: absolute;
inset: 0;
background-color: #ccc;
border: 1px solid #bbb;
border-radius: 1.5rem;
transition: all 0.3s ease;
cursor: pointer;
&::before {
content: "";
position: absolute;
top: 50%;
left: 0.125rem;
width: 1.25rem;
height: 1.25rem;
background-color: white;
border: 1px solid #ddd;
border-radius: 50%;
transform: translateY(-50%);
transition: all 0.3s ease;
}
}
input:checked + span {
background-color: #21ad0e;
border-color: #148605;
&::before {
transform: translateX(1rem) translateY(-50%);
}
}
&:hover span {
box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.2);
}
input:checked:hover + span {
box-shadow: 0 0 0 2px rgba(34, 197, 94, 0.3);
}
&:has(input:disabled) {
cursor: not-allowed;
}
input:disabled + span {
opacity: 0.5;
cursor: not-allowed;
}
}
}
[data-slot="reload-error"] {
display: flex;
align-items: center;
@@ -54,7 +254,8 @@
gap: var(--space-2);
margin: 0;
flex-shrink: 0;
padding: 0;
border: none;
}
}
}
@@ -1,17 +1,19 @@
import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router"
import { Show } from "solid-js"
import { json, action, useParams, createAsync, useSubmission } from "@solidjs/router"
import { createEffect, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { withActor } from "~/context/auth.withActor"
import { Billing } from "@opencode-ai/console-core/billing.js"
import { Database, eq } from "@opencode-ai/console-core/drizzle/index.js"
import { BillingTable } from "@opencode-ai/console-core/schema/billing.sql.js"
import styles from "./reload-section.module.css"
import { queryBillingInfo } from "../../common"
const reload = action(async (form: FormData) => {
"use server"
const workspaceID = form.get("workspaceID")?.toString()
if (!workspaceID) return { error: "Workspace ID is required" }
return json(await withActor(() => Billing.reload(), workspaceID), {
revalidate: getBillingInfo.key,
revalidate: queryBillingInfo.key,
})
}, "billing.reload")
@@ -20,12 +22,27 @@ const setReload = action(async (form: FormData) => {
const workspaceID = form.get("workspaceID")?.toString()
if (!workspaceID) return { error: "Workspace ID is required" }
const reloadValue = form.get("reload")?.toString() === "true"
const amountStr = form.get("reloadAmount")?.toString()
const triggerStr = form.get("reloadTrigger")?.toString()
const reloadAmount = amountStr && amountStr.trim() !== "" ? parseInt(amountStr) : null
const reloadTrigger = triggerStr && triggerStr.trim() !== "" ? parseInt(triggerStr) : null
if (reloadValue) {
if (reloadAmount === null || reloadAmount < Billing.RELOAD_AMOUNT_MIN)
return { error: `Reload amount must be at least $${Billing.RELOAD_AMOUNT_MIN}` }
if (reloadTrigger === null || reloadTrigger < Billing.RELOAD_TRIGGER_MIN)
return { error: `Balance trigger must be at least $${Billing.RELOAD_TRIGGER_MIN}` }
}
return json(
await Database.use((tx) =>
tx
.update(BillingTable)
.set({
reload: reloadValue,
...(reloadAmount !== null ? { reloadAmount } : {}),
...(reloadTrigger !== null ? { reloadTrigger } : {}),
...(reloadValue
? {
reloadError: null,
@@ -35,22 +52,47 @@ const setReload = action(async (form: FormData) => {
})
.where(eq(BillingTable.workspaceID, workspaceID)),
),
{ revalidate: getBillingInfo.key },
{ revalidate: queryBillingInfo.key },
)
}, "billing.setReload")
const getBillingInfo = query(async (workspaceID: string) => {
"use server"
return withActor(async () => {
return await Billing.get()
}, workspaceID)
}, "billing.get")
export function ReloadSection() {
const params = useParams()
const balanceInfo = createAsync(() => getBillingInfo(params.id))
const billingInfo = createAsync(() => queryBillingInfo(params.id))
const setReloadSubmission = useSubmission(setReload)
const reloadSubmission = useSubmission(reload)
const [store, setStore] = createStore({
show: false,
reload: false,
reloadAmount: "",
reloadTrigger: "",
})
createEffect(() => {
if (
!setReloadSubmission.pending &&
setReloadSubmission.result &&
!(setReloadSubmission.result as any).error
) {
setStore("show", false)
}
})
function show() {
while (true) {
setReloadSubmission.clear()
if (!setReloadSubmission.result) break
}
const info = billingInfo()!
setStore("show", true)
setStore("reload", info.reload ? true : true)
setStore("reloadAmount", info.reloadAmount.toString())
setStore("reloadTrigger", info.reloadTrigger.toString())
}
function hide() {
setStore("show", false)
}
return (
<section class={styles.root}>
@@ -58,43 +100,101 @@ export function ReloadSection() {
<h2>Auto Reload</h2>
<div data-slot="title-row">
<Show
when={balanceInfo()?.reload}
when={billingInfo()?.reload}
fallback={
<p>Auto reload is disabled. Enable to automatically reload when balance is low.</p>
<p>
Auto reload is <b>disabled</b>. Enable to automatically reload when balance is low.
</p>
}
>
<p>
We'll automatically reload <b>$20</b> (+$1.23 processing fee) when it reaches{" "}
<b>$5</b>.
Auto reload is <b>enabled</b>. We'll reload <b>${billingInfo()?.reloadAmount}</b>{" "}
(+$1.23 processing fee) when balance reaches <b>${billingInfo()?.reloadTrigger}</b>.
</p>
</Show>
<form action={setReload} method="post" data-slot="create-form">
<input type="hidden" name="workspaceID" value={params.id} />
<input type="hidden" name="reload" value={balanceInfo()?.reload ? "false" : "true"} />
<button data-color="primary" type="submit" disabled={setReloadSubmission.pending}>
<Show
when={balanceInfo()?.reload}
fallback={setReloadSubmission.pending ? "Enabling..." : "Enable"}
>
{setReloadSubmission.pending ? "Disabling..." : "Disable"}
</Show>
</button>
</form>
<button data-color="primary" type="button" onClick={() => show()}>
{billingInfo()?.reload ? "Edit" : "Enable"}
</button>
</div>
</div>
<div data-slot="section-content">
<Show when={balanceInfo()?.reload && balanceInfo()?.reloadError}>
<Show when={store.show}>
<form action={setReload} method="post" data-slot="create-form">
<div data-slot="form-field">
<label>
<span data-slot="field-label">Enable Auto Reload</span>
<div data-slot="toggle-container">
<label data-slot="model-toggle-label">
<input
type="checkbox"
name="reload"
value="true"
checked={store.reload}
onChange={(e) => setStore("reload", e.currentTarget.checked)}
/>
<span></span>
</label>
</div>
</label>
</div>
<div data-slot="input-row">
<div data-slot="input-field">
<p>Reload $</p>
<input
data-component="input"
name="reloadAmount"
type="number"
min={billingInfo()?.reloadAmountMin.toString()}
step="1"
value={store.reloadAmount}
onInput={(e) => setStore("reloadAmount", e.currentTarget.value)}
placeholder={billingInfo()?.reloadAmount.toString()}
disabled={!store.reload}
/>
</div>
<div data-slot="input-field">
<p>When balance reaches $</p>
<input
data-component="input"
name="reloadTrigger"
type="number"
min={billingInfo()?.reloadTriggerMin.toString()}
step="1"
value={store.reloadTrigger}
onInput={(e) => setStore("reloadTrigger", e.currentTarget.value)}
placeholder={billingInfo()?.reloadTrigger.toString()}
disabled={!store.reload}
/>
</div>
</div>
<Show when={setReloadSubmission.result && (setReloadSubmission.result as any).error}>
{(err: any) => <div data-slot="form-error">{err()}</div>}
</Show>
<input type="hidden" name="workspaceID" value={params.id} />
<div data-slot="form-actions">
<button type="button" data-color="ghost" onClick={() => hide()}>
Cancel
</button>
<button type="submit" data-color="primary" disabled={setReloadSubmission.pending}>
{setReloadSubmission.pending ? "Saving..." : "Save"}
</button>
</div>
</form>
</Show>
<Show when={billingInfo()?.reload && billingInfo()?.reloadError}>
<div data-slot="section-content">
<div data-slot="reload-error">
<p>
Reload failed at{" "}
{balanceInfo()?.timeReloadError!.toLocaleString("en-US", {
{billingInfo()?.timeReloadError!.toLocaleString("en-US", {
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
second: "2-digit",
})}
. Reason: {balanceInfo()?.reloadError?.replace(/\.$/, "")}. Please update your payment
. Reason: {billingInfo()?.reloadError?.replace(/\.$/, "")}. Please update your payment
method and try again.
</p>
<form action={reload} method="post" data-slot="create-form">
@@ -104,8 +204,8 @@ export function ReloadSection() {
</button>
</form>
</div>
</Show>
</div>
</div>
</Show>
</section>
)
}
@@ -1,22 +1,32 @@
import { Show, createMemo } from "solid-js"
import { createStore } from "solid-js/store"
import { createAsync, useParams, useAction, useSubmission } from "@solidjs/router"
import { NewUserSection } from "./new-user-section"
import { UsageSection } from "./usage-section"
import { ModelSection } from "./model-section"
import { ProviderSection } from "./provider-section"
import { IconLogo } from "~/component/icon"
import { createAsync, useParams, useAction, useSubmission } from "@solidjs/router"
import { querySessionInfo, queryBillingInfo, createCheckoutUrl } from "../common"
import { Show, createMemo } from "solid-js"
import { querySessionInfo, queryBillingInfo, createCheckoutUrl, formatBalance } from "../common"
export default function () {
const params = useParams()
const userInfo = createAsync(() => querySessionInfo(params.id))
const billingInfo = createAsync(() => queryBillingInfo(params.id))
const createCheckoutUrlAction = useAction(createCheckoutUrl)
const createCheckoutUrlSubmission = useSubmission(createCheckoutUrl)
const balanceAmount = createMemo(() => {
return ((billingInfo()?.balance ?? 0) / 100000000).toFixed(2)
const checkoutAction = useAction(createCheckoutUrl)
const checkoutSubmission = useSubmission(createCheckoutUrl)
const [store, setStore] = createStore({
checkoutRedirecting: false,
})
const balance = createMemo(() => formatBalance(billingInfo()?.balance ?? 0))
async function onClickCheckout() {
const baseUrl = window.location.href
const checkout = await checkoutAction(params.id, billingInfo()!.reloadAmount, baseUrl, baseUrl)
if (checkout && checkout.data) {
setStore("checkoutRedirecting", true)
window.location.href = checkout.data
}
}
return (
<div data-page="workspace-[id]">
@@ -38,21 +48,17 @@ export default function () {
<button
data-color="primary"
data-size="sm"
disabled={createCheckoutUrlSubmission.pending}
onClick={async () => {
const baseUrl = window.location.href
const checkoutUrl = await createCheckoutUrlAction(params.id, baseUrl, baseUrl)
if (checkoutUrl) {
window.location.href = checkoutUrl
}
}}
disabled={checkoutSubmission.pending || store.checkoutRedirecting}
onClick={onClickCheckout}
>
{createCheckoutUrlSubmission.pending ? "Loading..." : "Enable billing"}
{checkoutSubmission.pending || store.checkoutRedirecting
? "Loading..."
: "Enable billing"}
</button>
}
>
<span data-slot="balance">
Current balance <b>${balanceAmount() === "-0.00" ? "0.00" : balanceAmount()}</b>
Current balance <b>${balance()}</b>
</span>
</Show>
</span>
@@ -171,7 +171,6 @@
}
@media (max-width: 40rem) {
th,
td {
padding: var(--space-2) var(--space-3);
@@ -181,8 +180,7 @@
th {
&:nth-child(3)
/* Date */
{
/* Date */ {
display: none;
}
}
@@ -190,11 +188,10 @@
td {
&:nth-child(3)
/* Date */
{
/* Date */ {
display: none;
}
}
}
}
}
}
@@ -146,14 +146,20 @@ export function KeySection() {
title="Copy API key"
>
<span>{key.keyDisplay}</span>
<Show when={copied()} fallback={<IconCopy style={{ width: "14px", height: "14px" }} />}>
<Show
when={copied()}
fallback={<IconCopy style={{ width: "14px", height: "14px" }} />}
>
<IconCheck style={{ width: "14px", height: "14px" }} />
</Show>
</button>
</Show>
</td>
<td data-slot="key-user-email">{key.email}</td>
<td data-slot="key-last-used" title={key.timeUsed ? formatDateUTC(key.timeUsed) : undefined}>
<td
data-slot="key-last-used"
title={key.timeUsed ? formatDateUTC(key.timeUsed) : undefined}
>
{key.timeUsed ? formatDateForTable(key.timeUsed) : "-"}
</td>
<td data-slot="key-actions">
@@ -85,7 +85,12 @@ const updateMember = action(async (form: FormData) => {
)
}, "member.update")
function MemberRow(props: { member: any; workspaceID: string; actorID: string; actorRole: string }) {
function MemberRow(props: {
member: any
workspaceID: string
actorID: string
actorRole: string
}) {
const submission = useSubmission(updateMember)
const isCurrentUser = () => props.actorID === props.member.id
const isAdmin = () => props.actorRole === "admin"
@@ -69,4 +69,4 @@
}
}
}
}
}
@@ -31,6 +31,7 @@ const getModelsInfo = query(async (workspaceID: string) => {
return {
all: Object.entries(ZenData.list().models)
.filter(([id, _model]) => !["claude-3-5-haiku", "minimax-m2"].includes(id))
.filter(([id, _model]) => !id.startsWith("an-"))
.sort(([_idA, modelA], [_idB, modelB]) => modelA.name.localeCompare(modelB.name))
.map(([id, model]) => ({ id, name: model.name })),
disabled: await Model.listDisabled(),
@@ -140,4 +140,4 @@
}
}
}
}
}
@@ -43,15 +43,24 @@ export function NewUserSection() {
<div data-component="feature-grid">
<div data-slot="feature">
<h3>Tested & Verified Models</h3>
<p>We've benchmarked and tested models specifically for coding agents to ensure the best performance.</p>
<p>
We've benchmarked and tested models specifically for coding agents to ensure the best
performance.
</p>
</div>
<div data-slot="feature">
<h3>Highest Quality</h3>
<p>Access models configured for optimal performance - no downgrades or routing to cheaper providers.</p>
<p>
Access models configured for optimal performance - no downgrades or routing to cheaper
providers.
</p>
</div>
<div data-slot="feature">
<h3>No Lock-in</h3>
<p>Use Zen with any coding agent, and continue using other providers with opencode whenever you want.</p>
<p>
Use Zen with any coding agent, and continue using other providers with opencode
whenever you want.
</p>
</div>
</div>
@@ -128,7 +128,6 @@
}
@media (max-width: 40rem) {
th,
td {
padding: var(--space-2) var(--space-3);
@@ -136,4 +135,4 @@
}
}
}
}
}
@@ -22,7 +22,9 @@ const removeProvider = action(async (form: FormData) => {
if (!provider) return { error: "Provider is required" }
const workspaceID = form.get("workspaceID")?.toString()
if (!workspaceID) return { error: "Workspace ID is required" }
return json(await withActor(() => Provider.remove({ provider }), workspaceID), { revalidate: listProviders.key })
return json(await withActor(() => Provider.remove({ provider }), workspaceID), {
revalidate: listProviders.key,
})
}, "provider.remove")
const saveProvider = action(async (form: FormData) => {
@@ -53,7 +55,10 @@ const listProviders = query(async (workspaceID: string) => {
function ProviderRow(props: { provider: Provider }) {
const params = useParams()
const providers = createAsync(() => listProviders(params.id))
const saveSubmission = useSubmission(saveProvider, ([fd]) => fd.get("provider")?.toString() === props.provider.key)
const saveSubmission = useSubmission(
saveProvider,
([fd]) => fd.get("provider")?.toString() === props.provider.key,
)
const removeSubmission = useSubmission(
removeProvider,
([fd]) => fd.get("provider")?.toString() === props.provider.key,
@@ -89,9 +94,16 @@ function ProviderRow(props: { provider: Provider }) {
<td data-slot="provider-key">
<Show
when={store.editing}
fallback={<span>{providerData() ? maskCredentials(providerData()!.credentials) : "-"}</span>}
fallback={
<span>{providerData() ? maskCredentials(providerData()!.credentials) : "-"}</span>
}
>
<form id={`provider-form-${props.provider.key}`} action={saveProvider} method="post" data-slot="edit-form">
<form
id={`provider-form-${props.provider.key}`}
action={saveProvider}
method="post"
data-slot="edit-form"
>
<div data-slot="input-wrapper">
<input
ref={(r) => (input = r)}
@@ -31,7 +31,7 @@
margin: 0;
}
>button {
> button {
align-self: flex-start;
}
}
@@ -80,7 +80,7 @@
}
}
>button[type="reset"] {
> button[type="reset"] {
align-self: flex-start;
}
@@ -91,4 +91,4 @@
margin-top: calc(var(--space-1) * -1);
}
}
}
}
@@ -1,6 +1,6 @@
import { Resource } from "@opencode-ai/console-resource"
import { Actor } from "@opencode-ai/console-core/actor.js"
import { action, query } from "@solidjs/router"
import { action, json, query } from "@solidjs/router"
import { withActor } from "~/context/auth.withActor"
import { Billing } from "@opencode-ai/console-core/billing.js"
import { User } from "@opencode-ai/console-core/user.js"
@@ -34,6 +34,11 @@ export function formatDateUTC(date: Date) {
return date.toLocaleDateString("en-US", options)
}
export function formatBalance(amount: number) {
const balance = ((amount ?? 0) / 100000000).toFixed(2)
return balance === "-0.00" ? "0.00" : balance
}
export async function getLastSeenWorkspaceID() {
"use server"
return withActor(async () => {
@@ -71,14 +76,34 @@ export const querySessionInfo = query(async (workspaceID: string) => {
}, "session.get")
export const createCheckoutUrl = action(
async (workspaceID: string, successUrl: string, cancelUrl: string) => {
async (workspaceID: string, amount: number, successUrl: string, cancelUrl: string) => {
"use server"
return withActor(() => Billing.generateCheckoutUrl({ successUrl, cancelUrl }), workspaceID)
return json(
await withActor(
() =>
Billing.generateCheckoutUrl({ amount, successUrl, cancelUrl })
.then((data) => ({ error: undefined, data }))
.catch((e) => ({
error: e.message as string,
data: undefined,
})),
workspaceID,
),
)
},
"checkoutUrl",
)
export const queryBillingInfo = query(async (workspaceID: string) => {
"use server"
return withActor(() => Billing.get(), workspaceID)
return withActor(async () => {
const billing = await Billing.get()
return {
...billing,
reloadAmount: billing.reloadAmount ?? Billing.RELOAD_AMOUNT,
reloadAmountMin: Billing.RELOAD_AMOUNT_MIN,
reloadTrigger: billing.reloadTrigger ?? Billing.RELOAD_TRIGGER,
reloadTriggerMin: Billing.RELOAD_TRIGGER_MIN,
}
}, workspaceID)
}, "billing.get")
+96 -36
View File
@@ -28,7 +28,10 @@ export default function Home() {
createAsync(() => checkLoggedIn())
return (
<main data-page="zen">
<HttpHeader name="Cache-Control" value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400" />
<HttpHeader
name="Cache-Control"
value="public, max-age=1, s-maxage=3600, stale-while-revalidate=86400"
/>
<Title>OpenCode Zen | A curated set of reliable optimized models for coding agents</Title>
<Link rel="icon" type="image/svg+xml" href="/favicon-zen.svg" />
<Meta property="og:image" content="/social-share-zen.png" />
@@ -44,13 +47,19 @@ export default function Home() {
<img data-slot="zen logo dark" src={zenLogoDark} alt="zen logo dark" />
<strong>Reliable optimized models for coding agents</strong>
<p>
Zen gives you access to a curated set of AI models that OpenCode has tested and benchmarked specifically
for coding agents. No need to worry about inconsistent performance and quality, use validated models
that work.
Zen gives you access to a curated set of AI models that OpenCode has tested and
benchmarked specifically for coding agents. No need to worry about inconsistent
performance and quality, use validated models that work.
</p>
<div data-slot="model-logos">
<div>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<mask
id="mask0_79_128586"
style="mask-type:luminance"
@@ -71,8 +80,17 @@ export default function Home() {
</svg>
</div>
<div>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.7891 3.93164L20.2223 20.0677H23.7502L17.317 3.93164H13.7891Z" fill="currentColor" />
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13.7891 3.93164L20.2223 20.0677H23.7502L17.317 3.93164H13.7891Z"
fill="currentColor"
/>
<path
d="M6.32538 13.6824L8.52662 8.01177L10.7279 13.6824H6.32538ZM6.68225 3.93164L0.25 20.0677H3.84652L5.16202 16.6791H11.8914L13.2067 20.0677H16.8033L10.371 3.93164H6.68225Z"
fill="currentColor"
@@ -80,7 +98,13 @@ export default function Home() {
</svg>
</div>
<div>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.16861 16.0529L17.2018 9.85156C17.5957 9.54755 18.1586 9.66612 18.3463 10.1384C19.3339 12.6288 18.8926 15.6217 16.9276 17.6766C14.9626 19.7314 12.2285 20.1821 9.72948 19.1557L6.9995 20.4775C10.9151 23.2763 15.6699 22.5841 18.6411 19.4749C20.9979 17.0103 21.7278 13.6508 21.0453 10.6214L21.0515 10.6278C20.0617 6.17736 21.2948 4.39847 23.8207 0.760904C23.8804 0.674655 23.9402 0.588405 24 0.5L20.6762 3.97585V3.96506L9.16658 16.0551"
fill="currentColor"
@@ -92,7 +116,13 @@ export default function Home() {
</svg>
</div>
<div>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
@@ -102,7 +132,13 @@ export default function Home() {
</svg>
</div>
<div>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12.6241 11.346L20.3848 3.44816C20.5309 3.29931 20.4487 3 20.2601 3H16.0842C16.0388 3 15.9949 3.01897 15.9594 3.05541L7.59764 11.5629C7.46721 11.6944 7.27446 11.5771 7.27446 11.3666V3.25183C7.27446 3.11242 7.18515 3 7.07594 3H4.19843C4.08932 3 4 3.11242 4 3.25183V20.7482C4 20.8876 4.08932 21 4.19843 21H7.07594C7.18515 21 7.27446 20.8876 7.27446 20.7482V17.1834C7.27446 17.1073 7.30136 17.0344 7.34815 16.987L9.94075 14.3486C10.0031 14.2853 10.0895 14.2757 10.159 14.3232L17.0934 19.5573C18.2289 20.3412 19.4975 20.8226 20.786 20.9652C20.9008 20.9778 21 20.8606 21 20.7133V17.3559C21 17.2276 20.9249 17.1232 20.8243 17.1073C20.0659 16.9853 19.326 16.6845 18.6569 16.222L12.6538 11.764C12.5291 11.6785 12.5135 11.4584 12.6241 11.346Z"
fill="currentColor"
@@ -112,7 +148,13 @@ export default function Home() {
</div>
<a href="/auth">
<span>Get started with Zen </span>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.5 12L17 12M13 16.5L17.5 12L13 7.5"
stroke="currentColor"
@@ -124,14 +166,23 @@ export default function Home() {
</div>
<div data-slot="pricing-copy">
<p>
<strong>Add $20 Pay as you go balance</strong> <span>(+$1.23 card processing fee)</span>
<strong>Add $20 Pay as you go balance</strong>{" "}
<span>(+$1.23 card processing fee)</span>
</p>
<p>Use with any agent. Set monthly spend limits. Cancel any time.</p>
</div>
</section>
<section data-component="comparison">
<video src={compareVideo} autoplay playsinline loop muted preload="auto" poster={compareVideoPoster}>
<video
src={compareVideo}
autoplay
playsinline
loop
muted
preload="auto"
poster={compareVideoPoster}
>
Your browser does not support the video tag.
</video>
</section>
@@ -140,8 +191,8 @@ export default function Home() {
<div data-slot="section-title">
<h3>What problem is Zen solving?</h3>
<p>
There are so many models available, but only a few work well with coding agents. Most providers
configure them differently with varying results.
There are so many models available, but only a few work well with coding agents.
Most providers configure them differently with varying results.
</p>
</div>
<p>We're fixing this for everyone, not just OpenCode users.</p>
@@ -176,14 +227,15 @@ export default function Home() {
<li>
<span>[2]</span>
<div>
<strong>Use Zen with transparent pricing</strong> - <a href="/docs/zen/#pricing">pay per request</a>{" "}
with zero markups
<strong>Use Zen with transparent pricing</strong> -{" "}
<a href="/docs/zen/#pricing">pay per request</a> with zero markups
</div>
</li>
<li>
<span>[3]</span>
<div>
<strong>Auto-top up</strong> - when your balance reaches $5 well automatically add $20
<strong>Auto-top up</strong> - when your balance reaches $5 well automatically
add $20
</div>
</li>
</ul>
@@ -195,8 +247,9 @@ export default function Home() {
<div>
<span>[*]</span>
<p>
All Zen models are hosted in the US. Providers follow a zero-retention policy and do not use your data
for model training, with the <a href="/docs/zen/#privacy">following exceptions</a>.
All Zen models are hosted in the US. Providers follow a zero-retention policy and
do not use your data for model training, with the{" "}
<a href="/docs/zen/#privacy">following exceptions</a>.
</p>
</div>
</div>
@@ -251,7 +304,8 @@ export default function Home() {
<span>ex-Head of Design, Laravel</span>
</div>
<div data-slot="quote">
With <span>@OpenCode</span> Zen I know all the models are tested and perfect for coding agents.
With <span>@OpenCode</span> Zen I know all the models are tested and perfect for
coding agents.
</div>
</div>
</a>
@@ -275,38 +329,44 @@ export default function Home() {
<ul>
<li>
<Faq question="What is OpenCode Zen?">
Zen is a curated set of AI models tested and benchmarked for coding agents created by the team behind
OpenCode.
Zen is a curated set of AI models tested and benchmarked for coding agents created
by the team behind OpenCode.
</Faq>
</li>
<li>
<Faq question="What makes Zen more accurate?">
Zen only provides models that have been specifically tested and benchmarked for coding agents. You
wouldnt use a butter knife to cut steak, dont use poor models for coding.
Zen only provides models that have been specifically tested and benchmarked for
coding agents. You wouldnt use a butter knife to cut steak, dont use poor models
for coding.
</Faq>
</li>
<li>
<Faq question="Is Zen cheaper?">
Zen is not for profit. Zen passes through the costs from the model providers to you. The higher Zens
usage the more OpenCode can negotiate better rates and pass those to you.
Zen is not for profit. Zen passes through the costs from the model providers to
you. The higher Zens usage the more OpenCode can negotiate better rates and pass
those to you.
</Faq>
</li>
<li>
<Faq question="How much does Zen cost?">
Zen <a href="/docs/zen/#pricing">charges per request</a> with zero markups, so you pay exactly what
the model provider charges. Your total cost depends on usage, and you can set monthly spend limits in
your <a href="/auth">account</a>. To cover costs, OpenCode adds only a small payment processing fee of
$1.23 per $20 balance top-up.
Zen <a href="/docs/zen/#pricing">charges per request</a> with zero markups, so you
pay exactly what the model provider charges. Your total cost depends on usage, and
you can set monthly spend limits in your <a href="/auth">account</a>. To cover
costs, OpenCode adds only a small payment processing fee of $1.23 per $20 balance
top-up.
</Faq>
</li>
<li>
<Faq question="What about data and privacy?">
All Zen models are hosted in the US. Providers follow a zero-retention policy and do not use your data
for model training, with the <a href="/docs/zen/#privacy">following exceptions</a>.
All Zen models are hosted in the US. Providers follow a zero-retention policy and
do not use your data for model training, with the{" "}
<a href="/docs/zen/#privacy">following exceptions</a>.
</Faq>
</li>
<li>
<Faq question="Can I set spend limits?">Yes, you can set monthly spending limits in your account.</Faq>
<Faq question="Can I set spend limits?">
Yes, you can set monthly spending limits in your account.
</Faq>
</li>
<li>
<Faq question="Can I cancel?">
@@ -315,8 +375,8 @@ export default function Home() {
</li>
<li>
<Faq question="Can I use Zen with other coding agents?">
While Zen works great with OpenCode, you can use Zen with any agent. Follow the setup instructions in
your preferred coding agent.
While Zen works great with OpenCode, you can use Zen with any agent. Follow the
setup instructions in your preferred coding agent.
</Faq>
</li>
</ul>
@@ -281,6 +281,7 @@ export async function handler(
monthlyLimit: BillingTable.monthlyLimit,
monthlyUsage: BillingTable.monthlyUsage,
timeMonthlyUsageUpdated: BillingTable.timeMonthlyUsageUpdated,
reloadTrigger: BillingTable.reloadTrigger,
},
user: {
id: UserTable.id,
@@ -532,7 +533,10 @@ export async function handler(
and(
eq(BillingTable.workspaceID, authInfo.workspaceID),
eq(BillingTable.reload, true),
lt(BillingTable.balance, centsToMicroCents(Billing.CHARGE_THRESHOLD)),
lt(
BillingTable.balance,
centsToMicroCents((authInfo.billing.reloadTrigger ?? Billing.RELOAD_TRIGGER) * 100),
),
or(
isNull(BillingTable.timeReloadLockedTill),
lt(BillingTable.timeReloadLockedTill, sql`now()`),
@@ -98,7 +98,10 @@ export function fromAnthropicRequest(body: any): CommonRequest {
typeof (src as any).media_type === "string" &&
typeof (src as any).data === "string"
)
return { type: "image_url", image_url: { url: `data:${(src as any).media_type};base64,${(src as any).data}` } }
return {
type: "image_url",
image_url: { url: `data:${(src as any).media_type};base64,${(src as any).data}` },
}
return undefined
}
@@ -120,12 +123,15 @@ export function fromAnthropicRequest(body: any): CommonRequest {
if ((p as any).type === "tool_result") {
const id = (p as any).tool_use_id
const content =
typeof (p as any).content === "string" ? (p as any).content : JSON.stringify((p as any).content)
typeof (p as any).content === "string"
? (p as any).content
: JSON.stringify((p as any).content)
msgs.push({ role: "tool", tool_call_id: id, content })
}
}
if (partsOut.length > 0) {
if (partsOut.length === 1 && partsOut[0].type === "text") msgs.push({ role: "user", content: partsOut[0].text })
if (partsOut.length === 1 && partsOut[0].type === "text")
msgs.push({ role: "user", content: partsOut[0].text })
else msgs.push({ role: "user", content: partsOut })
}
continue
@@ -137,7 +143,8 @@ export function fromAnthropicRequest(body: any): CommonRequest {
const tcs: any[] = []
for (const p of partsIn) {
if (!p || !(p as any).type) continue
if ((p as any).type === "text" && typeof (p as any).text === "string") texts.push((p as any).text)
if ((p as any).type === "text" && typeof (p as any).text === "string")
texts.push((p as any).text)
if ((p as any).type === "tool_use") {
const name = (p as any).name
const id = (p as any).id
@@ -165,7 +172,11 @@ export function fromAnthropicRequest(body: any): CommonRequest {
.filter((t: any) => t && typeof t === "object" && "input_schema" in t)
.map((t: any) => ({
type: "function",
function: { name: (t as any).name, description: (t as any).description, parameters: (t as any).input_schema },
function: {
name: (t as any).name,
description: (t as any).description,
parameters: (t as any).input_schema,
},
}))
: undefined
@@ -203,7 +214,9 @@ export function fromAnthropicRequest(body: any): CommonRequest {
export function toAnthropicRequest(body: CommonRequest) {
if (!body || typeof body !== "object") return body
const sysIn = Array.isArray(body.messages) ? body.messages.filter((m: any) => m && m.role === "system") : []
const sysIn = Array.isArray(body.messages)
? body.messages.filter((m: any) => m && m.role === "system")
: []
let ccCount = 0
const cc = () => {
ccCount++
@@ -354,7 +367,9 @@ export function fromAnthropicResponse(resp: any): CommonResponse {
const idIn = (resp as any).id
const id =
typeof idIn === "string" ? idIn.replace(/^msg_/, "chatcmpl_") : `chatcmpl_${Math.random().toString(36).slice(2)}`
typeof idIn === "string"
? idIn.replace(/^msg_/, "chatcmpl_")
: `chatcmpl_${Math.random().toString(36).slice(2)}`
const model = (resp as any).model
const blocks: any[] = Array.isArray((resp as any).content) ? (resp as any).content : []
@@ -397,7 +412,9 @@ export function fromAnthropicResponse(resp: any): CommonResponse {
const ct = typeof (u as any).output_tokens === "number" ? (u as any).output_tokens : undefined
const total = pt != null && ct != null ? pt + ct : undefined
const cached =
typeof (u as any).cache_read_input_tokens === "number" ? (u as any).cache_read_input_tokens : undefined
typeof (u as any).cache_read_input_tokens === "number"
? (u as any).cache_read_input_tokens
: undefined
const details = cached != null ? { cached_tokens: cached } : undefined
return {
prompt_tokens: pt,
@@ -452,7 +469,12 @@ export function toAnthropicResponse(resp: CommonResponse) {
} catch {
input = (tc as any).function.arguments
}
content.push({ type: "tool_use", id: (tc as any).id, name: (tc as any).function.name, input })
content.push({
type: "tool_use",
id: (tc as any).id,
name: (tc as any).function.name,
input,
})
}
}
}
@@ -511,13 +533,22 @@ export function fromAnthropicChunk(chunk: string): CommonChunk | string {
if (json.type === "content_block_start") {
const cb = json.content_block
if (cb?.type === "text") {
out.choices.push({ index: json.index ?? 0, delta: { role: "assistant", content: "" }, finish_reason: null })
out.choices.push({
index: json.index ?? 0,
delta: { role: "assistant", content: "" },
finish_reason: null,
})
} else if (cb?.type === "tool_use") {
out.choices.push({
index: json.index ?? 0,
delta: {
tool_calls: [
{ index: json.index ?? 0, id: cb.id, type: "function", function: { name: cb.name, arguments: "" } },
{
index: json.index ?? 0,
id: cb.id,
type: "function",
function: { name: cb.name, arguments: "" },
},
],
},
finish_reason: null,
@@ -532,7 +563,9 @@ export function fromAnthropicChunk(chunk: string): CommonChunk | string {
} else if (d?.type === "input_json_delta") {
out.choices.push({
index: json.index ?? 0,
delta: { tool_calls: [{ index: json.index ?? 0, function: { arguments: d.partial_json } }] },
delta: {
tool_calls: [{ index: json.index ?? 0, function: { arguments: d.partial_json } }],
},
finish_reason: null,
})
}
@@ -558,7 +591,9 @@ export function fromAnthropicChunk(chunk: string): CommonChunk | string {
prompt_tokens: u.input_tokens,
completion_tokens: u.output_tokens,
total_tokens: (u.input_tokens || 0) + (u.output_tokens || 0),
...(u.cache_read_input_tokens ? { prompt_tokens_details: { cached_tokens: u.cache_read_input_tokens } } : {}),
...(u.cache_read_input_tokens
? { prompt_tokens_details: { cached_tokens: u.cache_read_input_tokens } }
: {}),
}
}
@@ -57,7 +57,8 @@ export const oaCompatHelper = {
const inputTokens = usage.prompt_tokens ?? 0
const outputTokens = usage.completion_tokens ?? 0
const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens ?? undefined
const cacheReadTokens = usage.cached_tokens ?? usage.prompt_tokens_details?.cached_tokens ?? undefined
const cacheReadTokens =
usage.cached_tokens ?? usage.prompt_tokens_details?.cached_tokens ?? undefined
return {
inputTokens: inputTokens - (cacheReadTokens ?? 0),
outputTokens,
@@ -79,7 +80,8 @@ export function fromOaCompatibleRequest(body: any): CommonRequest {
if (!m || !m.role) continue
if (m.role === "system") {
if (typeof m.content === "string" && m.content.length > 0) msgsOut.push({ role: "system", content: m.content })
if (typeof m.content === "string" && m.content.length > 0)
msgsOut.push({ role: "system", content: m.content })
continue
}
@@ -90,10 +92,12 @@ export function fromOaCompatibleRequest(body: any): CommonRequest {
const parts: any[] = []
for (const p of m.content) {
if (!p || !p.type) continue
if (p.type === "text" && typeof p.text === "string") parts.push({ type: "text", text: p.text })
if (p.type === "text" && typeof p.text === "string")
parts.push({ type: "text", text: p.text })
if (p.type === "image_url") parts.push({ type: "image_url", image_url: p.image_url })
}
if (parts.length === 1 && parts[0].type === "text") msgsOut.push({ role: "user", content: parts[0].text })
if (parts.length === 1 && parts[0].type === "text")
msgsOut.push({ role: "user", content: parts[0].text })
else if (parts.length > 0) msgsOut.push({ role: "user", content: parts })
}
continue
@@ -137,7 +141,8 @@ export function toOaCompatibleRequest(body: CommonRequest) {
if (p.type === "image_url" && p.image_url) return { type: "image_url", image_url: p.image_url }
const s = (p as any).source
if (!s || typeof s !== "object") return undefined
if (s.type === "url" && typeof s.url === "string") return { type: "image_url", image_url: { url: s.url } }
if (s.type === "url" && typeof s.url === "string")
return { type: "image_url", image_url: { url: s.url } }
if (s.type === "base64" && typeof s.media_type === "string" && typeof s.data === "string")
return { type: "image_url", image_url: { url: `data:${s.media_type};base64,${s.data}` } }
return undefined
@@ -147,7 +152,8 @@ export function toOaCompatibleRequest(body: CommonRequest) {
if (!m || !m.role) continue
if (m.role === "system") {
if (typeof m.content === "string" && m.content.length > 0) msgsOut.push({ role: "system", content: m.content })
if (typeof m.content === "string" && m.content.length > 0)
msgsOut.push({ role: "system", content: m.content })
continue
}
@@ -160,11 +166,13 @@ export function toOaCompatibleRequest(body: CommonRequest) {
const parts: any[] = []
for (const p of m.content) {
if (!p || !p.type) continue
if (p.type === "text" && typeof p.text === "string") parts.push({ type: "text", text: p.text })
if (p.type === "text" && typeof p.text === "string")
parts.push({ type: "text", text: p.text })
const ip = toImg(p)
if (ip) parts.push(ip)
}
if (parts.length === 1 && parts[0].type === "text") msgsOut.push({ role: "user", content: parts[0].text })
if (parts.length === 1 && parts[0].type === "text")
msgsOut.push({ role: "user", content: parts[0].text })
else if (parts.length > 0) msgsOut.push({ role: "user", content: parts })
}
continue
@@ -317,7 +325,9 @@ export function toOaCompatibleResponse(resp: CommonResponse) {
const idIn = (resp as any).id
const id =
typeof idIn === "string" ? idIn.replace(/^msg_/, "chatcmpl_") : `chatcmpl_${Math.random().toString(36).slice(2)}`
typeof idIn === "string"
? idIn.replace(/^msg_/, "chatcmpl_")
: `chatcmpl_${Math.random().toString(36).slice(2)}`
const model = (resp as any).model
const blocks: any[] = Array.isArray((resp as any).content) ? (resp as any).content : []
@@ -359,7 +369,8 @@ export function toOaCompatibleResponse(resp: CommonResponse) {
const pt = typeof u.input_tokens === "number" ? u.input_tokens : undefined
const ct = typeof u.output_tokens === "number" ? u.output_tokens : undefined
const total = pt != null && ct != null ? pt + ct : undefined
const cached = typeof u.cache_read_input_tokens === "number" ? u.cache_read_input_tokens : undefined
const cached =
typeof u.cache_read_input_tokens === "number" ? u.cache_read_input_tokens : undefined
const details = cached != null ? { cached_tokens: cached } : undefined
return {
prompt_tokens: pt,
@@ -532,7 +543,9 @@ export function toOaCompatibleChunk(chunk: CommonChunk): string {
total_tokens: chunk.usage.total_tokens,
...(chunk.usage.prompt_tokens_details?.cached_tokens
? {
prompt_tokens_details: { cached_tokens: chunk.usage.prompt_tokens_details.cached_tokens },
prompt_tokens_details: {
cached_tokens: chunk.usage.prompt_tokens_details.cached_tokens,
},
}
: {}),
}
@@ -77,13 +77,20 @@ export function fromOpenaiRequest(body: any): CommonRequest {
typeof (s as any).media_type === "string" &&
typeof (s as any).data === "string"
)
return { type: "image_url", image_url: { url: `data:${(s as any).media_type};base64,${(s as any).data}` } }
return {
type: "image_url",
image_url: { url: `data:${(s as any).media_type};base64,${(s as any).data}` },
}
return undefined
}
const msgs: any[] = []
const inMsgs = Array.isArray(body.input) ? body.input : Array.isArray(body.messages) ? body.messages : []
const inMsgs = Array.isArray(body.input)
? body.input
: Array.isArray(body.messages)
? body.messages
: []
for (const m of inMsgs) {
if (!m) continue
@@ -96,7 +103,9 @@ export function fromOpenaiRequest(body: any): CommonRequest {
const args = typeof a === "string" ? a : JSON.stringify(a ?? {})
msgs.push({
role: "assistant",
tool_calls: [{ id: (m as any).id, type: "function", function: { name, arguments: args } }],
tool_calls: [
{ id: (m as any).id, type: "function", function: { name, arguments: args } },
],
})
}
if ((m as any).type === "function_call_output") {
@@ -113,7 +122,8 @@ export function fromOpenaiRequest(body: any): CommonRequest {
if (typeof c === "string" && c.length > 0) msgs.push({ role: "system", content: c })
if (Array.isArray(c)) {
const t = c.find((p: any) => p && typeof p.text === "string")
if (t && typeof t.text === "string" && t.text.length > 0) msgs.push({ role: "system", content: t.text })
if (t && typeof t.text === "string" && t.text.length > 0)
msgs.push({ role: "system", content: t.text })
}
continue
}
@@ -126,18 +136,24 @@ export function fromOpenaiRequest(body: any): CommonRequest {
const parts: any[] = []
for (const p of c) {
if (!p || !(p as any).type) continue
if (((p as any).type === "text" || (p as any).type === "input_text") && typeof (p as any).text === "string")
if (
((p as any).type === "text" || (p as any).type === "input_text") &&
typeof (p as any).text === "string"
)
parts.push({ type: "text", text: (p as any).text })
const ip = toImg(p)
if (ip) parts.push(ip)
if ((p as any).type === "tool_result") {
const id = (p as any).tool_call_id
const content =
typeof (p as any).content === "string" ? (p as any).content : JSON.stringify((p as any).content)
typeof (p as any).content === "string"
? (p as any).content
: JSON.stringify((p as any).content)
msgs.push({ role: "tool", tool_call_id: id, content })
}
}
if (parts.length === 1 && parts[0].type === "text") msgs.push({ role: "user", content: parts[0].text })
if (parts.length === 1 && parts[0].type === "text")
msgs.push({ role: "user", content: parts[0].text })
else if (parts.length > 0) msgs.push({ role: "user", content: parts })
}
continue
@@ -153,7 +169,11 @@ export function fromOpenaiRequest(body: any): CommonRequest {
}
if ((m as any).role === "tool") {
msgs.push({ role: "tool", tool_call_id: (m as any).tool_call_id, content: (m as any).content })
msgs.push({
role: "tool",
tool_call_id: (m as any).tool_call_id,
content: (m as any).content,
})
continue
}
}
@@ -210,7 +230,10 @@ export function toOpenaiRequest(body: CommonRequest) {
typeof (s as any).media_type === "string" &&
typeof (s as any).data === "string"
)
return { type: "input_image", image_url: { url: `data:${(s as any).media_type};base64,${(s as any).data}` } }
return {
type: "input_image",
image_url: { url: `data:${(s as any).media_type};base64,${(s as any).data}` },
}
return undefined
}
@@ -257,7 +280,10 @@ export function toOpenaiRequest(body: CommonRequest) {
}
if ((m as any).role === "tool") {
const out = typeof (m as any).content === "string" ? (m as any).content : JSON.stringify((m as any).content)
const out =
typeof (m as any).content === "string"
? (m as any).content
: JSON.stringify((m as any).content)
input.push({ type: "function_call_output", call_id: (m as any).tool_call_id, output: out })
continue
}
@@ -325,7 +351,9 @@ export function fromOpenaiResponse(resp: any): CommonResponse {
const idIn = (r as any).id
const id =
typeof idIn === "string" ? idIn.replace(/^resp_/, "chatcmpl_") : `chatcmpl_${Math.random().toString(36).slice(2)}`
typeof idIn === "string"
? idIn.replace(/^resp_/, "chatcmpl_")
: `chatcmpl_${Math.random().toString(36).slice(2)}`
const model = (r as any).model ?? (resp as any).model
const out = Array.isArray((r as any).output) ? (r as any).output : []
@@ -452,7 +480,9 @@ export function toOpenaiResponse(resp: CommonResponse) {
})()
return {
id: (resp as any).id?.replace(/^chatcmpl_/, "resp_") ?? `resp_${Math.random().toString(36).slice(2)}`,
id:
(resp as any).id?.replace(/^chatcmpl_/, "resp_") ??
`resp_${Math.random().toString(36).slice(2)}`,
object: "response",
model: (resp as any).model,
output: outputItems,
@@ -498,7 +528,9 @@ export function fromOpenaiChunk(chunk: string): CommonChunk | string {
if (typeof name === "string" && name.length > 0) {
out.choices.push({
index: 0,
delta: { tool_calls: [{ index: 0, id, type: "function", function: { name, arguments: "" } }] },
delta: {
tool_calls: [{ index: 0, id, type: "function", function: { name, arguments: "" } }],
},
finish_reason: null,
})
}
@@ -555,7 +587,12 @@ export function toOpenaiChunk(chunk: CommonChunk): string {
const model = chunk.model
if (d.content) {
const data = { id, type: "response.output_text.delta", delta: d.content, response: { id, model } }
const data = {
id,
type: "response.output_text.delta",
delta: d.content,
response: { id, model },
}
return `event: response.output_text.delta\ndata: ${JSON.stringify(data)}`
}
@@ -565,7 +602,13 @@ export function toOpenaiChunk(chunk: CommonChunk): string {
const data = {
type: "response.output_item.added",
output_index: 0,
item: { id: tc.id, type: "function_call", name: tc.function.name, call_id: tc.id, arguments: "" },
item: {
id: tc.id,
type: "function_call",
name: tc.function.name,
call_id: tc.id,
arguments: "",
},
}
return `event: response.output_item.added\ndata: ${JSON.stringify(data)}`
}
@@ -593,7 +636,11 @@ export function toOpenaiChunk(chunk: CommonChunk): string {
}
: undefined
const data: any = { id, type: "response.completed", response: { id, model, ...(usage ? { usage } : {}) } }
const data: any = {
id,
type: "response.completed",
response: { id, model, ...(usage ? { usage } : {}) },
}
return `event: response.completed\ndata: ${JSON.stringify(data)}`
}
@@ -15,6 +15,7 @@ body {
--font-size-9xl: 8rem;
--font-mono:
"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
"IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace;
--font-sans: var(--font-mono);
}
+1 -1
View File
@@ -6,4 +6,4 @@
/// <reference path="../../../sst-env.d.ts" />
import "sst"
export {}
export {}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -180,9 +178,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -190,10 +186,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -280,10 +273,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -398,10 +388,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -486,17 +473,12 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
},
"name": {
"name": "name",
"columns": [
"workspace_id",
"name"
],
"columns": ["workspace_id", "name"],
"isUnique": true
}
},
@@ -504,10 +486,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -599,10 +578,7 @@
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
}
},
@@ -610,10 +586,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -670,9 +643,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -680,9 +651,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -699,4 +668,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -180,9 +178,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -190,10 +186,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -280,10 +273,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -398,10 +388,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -486,17 +473,12 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
},
"name": {
"name": "name",
"columns": [
"workspace_id",
"name"
],
"columns": ["workspace_id", "name"],
"isUnique": true
}
},
@@ -504,10 +486,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -599,10 +578,7 @@
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
}
},
@@ -610,10 +586,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -670,9 +643,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -680,9 +651,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -699,4 +668,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -180,9 +178,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -190,10 +186,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -280,10 +273,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -398,10 +388,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -486,17 +473,12 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
},
"name": {
"name": "name",
"columns": [
"workspace_id",
"name"
],
"columns": ["workspace_id", "name"],
"isUnique": true
}
},
@@ -504,10 +486,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -592,10 +571,7 @@
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
}
},
@@ -603,10 +579,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -663,9 +636,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -673,9 +644,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -692,4 +661,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -180,9 +178,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -190,10 +186,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -280,10 +273,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -398,10 +388,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -486,17 +473,12 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
},
"name": {
"name": "name",
"columns": [
"workspace_id",
"name"
],
"columns": ["workspace_id", "name"],
"isUnique": true
}
},
@@ -504,10 +486,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -599,10 +578,7 @@
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
}
},
@@ -610,10 +586,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -670,9 +643,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -680,9 +651,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -699,4 +668,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -180,9 +178,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -190,10 +186,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -280,10 +273,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -398,10 +388,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -486,17 +473,12 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
},
"name": {
"name": "name",
"columns": [
"workspace_id",
"name"
],
"columns": ["workspace_id", "name"],
"isUnique": true
}
},
@@ -504,10 +486,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -613,18 +592,12 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
}
},
@@ -632,10 +605,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -692,9 +662,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -702,9 +670,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -721,4 +687,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -180,9 +178,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -190,10 +186,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -280,10 +273,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -398,10 +388,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -486,17 +473,12 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
},
"name": {
"name": "name",
"columns": [
"workspace_id",
"name"
],
"columns": ["workspace_id", "name"],
"isUnique": true
}
},
@@ -504,10 +486,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -613,32 +592,22 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": false
}
},
@@ -646,10 +615,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -706,9 +672,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -716,9 +680,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -735,4 +697,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -180,9 +178,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -190,10 +186,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -280,10 +273,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -398,10 +388,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -486,17 +473,12 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
},
"name": {
"name": "name",
"columns": [
"workspace_id",
"name"
],
"columns": ["workspace_id", "name"],
"isUnique": true
}
},
@@ -504,10 +486,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -599,32 +578,22 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": false
}
},
@@ -632,10 +601,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -692,9 +658,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -702,9 +666,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -721,4 +683,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -180,9 +178,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -190,10 +186,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -280,10 +273,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -398,10 +388,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -493,17 +480,12 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
},
"name": {
"name": "name",
"columns": [
"workspace_id",
"name"
],
"columns": ["workspace_id", "name"],
"isUnique": true
}
},
@@ -511,10 +493,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -606,32 +585,22 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": false
}
},
@@ -639,10 +608,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -699,9 +665,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -709,9 +673,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -728,4 +690,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -180,9 +178,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -190,10 +186,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -280,10 +273,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -398,10 +388,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -493,9 +480,7 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
}
},
@@ -503,10 +488,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -598,32 +580,22 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": false
}
},
@@ -631,10 +603,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -691,9 +660,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -701,9 +668,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -720,4 +685,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -180,9 +178,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -190,10 +186,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -280,10 +273,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -398,10 +388,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -479,9 +466,7 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
}
},
@@ -489,10 +474,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -584,32 +566,22 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": false
}
},
@@ -617,10 +589,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -677,9 +646,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -687,9 +654,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -706,4 +671,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -180,9 +178,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -190,10 +186,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -280,10 +273,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -398,10 +388,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -479,9 +466,7 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
}
},
@@ -489,10 +474,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -584,32 +566,22 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": false
}
},
@@ -617,10 +589,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -677,9 +646,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -687,9 +654,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -706,4 +671,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -180,9 +178,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -190,10 +186,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -280,10 +273,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -398,10 +388,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -479,9 +466,7 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
}
},
@@ -489,10 +474,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -605,32 +587,22 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": false
}
},
@@ -638,10 +610,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -698,9 +667,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -708,9 +675,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -727,4 +692,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -180,9 +178,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -190,10 +186,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -280,10 +273,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -398,10 +388,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -479,9 +466,7 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
}
},
@@ -489,10 +474,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -549,10 +531,7 @@
"indexes": {
"model_workspace_model": {
"name": "model_workspace_model",
"columns": [
"workspace_id",
"model"
],
"columns": ["workspace_id", "model"],
"isUnique": true
}
},
@@ -560,10 +539,7 @@
"compositePrimaryKeys": {
"model_workspace_id_id_pk": {
"name": "model_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -676,32 +652,22 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": false
}
},
@@ -709,10 +675,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -769,9 +732,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -779,9 +740,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -798,4 +757,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -180,9 +178,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -190,10 +186,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -280,10 +273,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -398,10 +388,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -479,9 +466,7 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
}
},
@@ -489,10 +474,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -549,10 +531,7 @@
"indexes": {
"model_workspace_model": {
"name": "model_workspace_model",
"columns": [
"workspace_id",
"model"
],
"columns": ["workspace_id", "model"],
"isUnique": true
}
},
@@ -560,10 +539,7 @@
"compositePrimaryKeys": {
"model_workspace_id_id_pk": {
"name": "model_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -627,10 +603,7 @@
"indexes": {
"workspace_provider": {
"name": "workspace_provider",
"columns": [
"workspace_id",
"provider"
],
"columns": ["workspace_id", "provider"],
"isUnique": true
}
},
@@ -638,10 +611,7 @@
"compositePrimaryKeys": {
"provider_workspace_id_id_pk": {
"name": "provider_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -754,32 +724,22 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": false
}
},
@@ -787,10 +747,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -847,9 +804,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -857,9 +812,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -876,4 +829,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -180,9 +178,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -190,10 +186,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -280,10 +273,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -405,10 +395,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -486,9 +473,7 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
}
},
@@ -496,10 +481,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -556,10 +538,7 @@
"indexes": {
"model_workspace_model": {
"name": "model_workspace_model",
"columns": [
"workspace_id",
"model"
],
"columns": ["workspace_id", "model"],
"isUnique": true
}
},
@@ -567,10 +546,7 @@
"compositePrimaryKeys": {
"model_workspace_id_id_pk": {
"name": "model_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -634,10 +610,7 @@
"indexes": {
"workspace_provider": {
"name": "workspace_provider",
"columns": [
"workspace_id",
"provider"
],
"columns": ["workspace_id", "provider"],
"isUnique": true
}
},
@@ -645,10 +618,7 @@
"compositePrimaryKeys": {
"provider_workspace_id_id_pk": {
"name": "provider_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -761,32 +731,22 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": false
}
},
@@ -794,10 +754,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -854,9 +811,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -864,9 +819,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -883,4 +836,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -187,9 +185,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -197,10 +193,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -287,10 +280,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -412,10 +402,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -493,9 +480,7 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
}
},
@@ -503,10 +488,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -563,10 +545,7 @@
"indexes": {
"model_workspace_model": {
"name": "model_workspace_model",
"columns": [
"workspace_id",
"model"
],
"columns": ["workspace_id", "model"],
"isUnique": true
}
},
@@ -574,10 +553,7 @@
"compositePrimaryKeys": {
"model_workspace_id_id_pk": {
"name": "model_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -641,10 +617,7 @@
"indexes": {
"workspace_provider": {
"name": "workspace_provider",
"columns": [
"workspace_id",
"provider"
],
"columns": ["workspace_id", "provider"],
"isUnique": true
}
},
@@ -652,10 +625,7 @@
"compositePrimaryKeys": {
"provider_workspace_id_id_pk": {
"name": "provider_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -768,32 +738,22 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": false
}
},
@@ -801,10 +761,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -861,9 +818,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -871,9 +826,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -890,4 +843,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -48,9 +48,7 @@
"indexes": {
"email": {
"name": "email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": true
}
},
@@ -117,10 +115,7 @@
"indexes": {
"provider": {
"name": "provider",
"columns": [
"provider",
"subject"
],
"columns": ["provider", "subject"],
"isUnique": true
}
},
@@ -257,9 +252,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -267,10 +260,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -357,10 +347,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -482,10 +469,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -563,9 +547,7 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
}
},
@@ -573,10 +555,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -633,10 +612,7 @@
"indexes": {
"model_workspace_model": {
"name": "model_workspace_model",
"columns": [
"workspace_id",
"model"
],
"columns": ["workspace_id", "model"],
"isUnique": true
}
},
@@ -644,10 +620,7 @@
"compositePrimaryKeys": {
"model_workspace_id_id_pk": {
"name": "model_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -711,10 +684,7 @@
"indexes": {
"workspace_provider": {
"name": "workspace_provider",
"columns": [
"workspace_id",
"provider"
],
"columns": ["workspace_id", "provider"],
"isUnique": true
}
},
@@ -722,10 +692,7 @@
"compositePrimaryKeys": {
"provider_workspace_id_id_pk": {
"name": "provider_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -838,32 +805,22 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": false
}
},
@@ -871,10 +828,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -931,9 +885,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -941,9 +893,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -960,4 +910,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -102,17 +102,12 @@
"indexes": {
"provider": {
"name": "provider",
"columns": [
"provider",
"subject"
],
"columns": ["provider", "subject"],
"isUnique": true
},
"account_id": {
"name": "account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
}
},
@@ -249,9 +244,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -259,10 +252,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -349,10 +339,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -474,10 +461,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -555,9 +539,7 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
}
},
@@ -565,10 +547,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -625,10 +604,7 @@
"indexes": {
"model_workspace_model": {
"name": "model_workspace_model",
"columns": [
"workspace_id",
"model"
],
"columns": ["workspace_id", "model"],
"isUnique": true
}
},
@@ -636,10 +612,7 @@
"compositePrimaryKeys": {
"model_workspace_id_id_pk": {
"name": "model_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -703,10 +676,7 @@
"indexes": {
"workspace_provider": {
"name": "workspace_provider",
"columns": [
"workspace_id",
"provider"
],
"columns": ["workspace_id", "provider"],
"isUnique": true
}
},
@@ -714,10 +684,7 @@
"compositePrimaryKeys": {
"provider_workspace_id_id_pk": {
"name": "provider_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -830,32 +797,22 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": false
}
},
@@ -863,10 +820,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -923,9 +877,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -933,9 +885,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -952,4 +902,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -43,9 +43,7 @@
"compositePrimaryKeys": {
"account_id_pk": {
"name": "account_id_pk",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -109,17 +107,12 @@
"indexes": {
"provider": {
"name": "provider",
"columns": [
"provider",
"subject"
],
"columns": ["provider", "subject"],
"isUnique": true
},
"account_id": {
"name": "account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
}
},
@@ -127,9 +120,7 @@
"compositePrimaryKeys": {
"auth_id_pk": {
"name": "auth_id_pk",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -263,9 +254,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -273,10 +262,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -363,10 +349,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -488,10 +471,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -569,9 +549,7 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
}
},
@@ -579,10 +557,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -639,10 +614,7 @@
"indexes": {
"model_workspace_model": {
"name": "model_workspace_model",
"columns": [
"workspace_id",
"model"
],
"columns": ["workspace_id", "model"],
"isUnique": true
}
},
@@ -650,10 +622,7 @@
"compositePrimaryKeys": {
"model_workspace_id_id_pk": {
"name": "model_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -717,10 +686,7 @@
"indexes": {
"workspace_provider": {
"name": "workspace_provider",
"columns": [
"workspace_id",
"provider"
],
"columns": ["workspace_id", "provider"],
"isUnique": true
}
},
@@ -728,10 +694,7 @@
"compositePrimaryKeys": {
"provider_workspace_id_id_pk": {
"name": "provider_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -844,32 +807,22 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": false
}
},
@@ -877,10 +830,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -937,9 +887,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -947,9 +895,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -966,4 +912,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -43,9 +43,7 @@
"compositePrimaryKeys": {
"account_id_pk": {
"name": "account_id_pk",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -109,17 +107,12 @@
"indexes": {
"provider": {
"name": "provider",
"columns": [
"provider",
"subject"
],
"columns": ["provider", "subject"],
"isUnique": true
},
"account_id": {
"name": "account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
}
},
@@ -127,9 +120,7 @@
"compositePrimaryKeys": {
"auth_id_pk": {
"name": "auth_id_pk",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -277,9 +268,7 @@
"indexes": {
"global_customer_id": {
"name": "global_customer_id",
"columns": [
"customer_id"
],
"columns": ["customer_id"],
"isUnique": true
}
},
@@ -287,10 +276,7 @@
"compositePrimaryKeys": {
"billing_workspace_id_id_pk": {
"name": "billing_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -377,10 +363,7 @@
"compositePrimaryKeys": {
"payment_workspace_id_id_pk": {
"name": "payment_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -502,10 +485,7 @@
"compositePrimaryKeys": {
"usage_workspace_id_id_pk": {
"name": "usage_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -583,9 +563,7 @@
"indexes": {
"global_key": {
"name": "global_key",
"columns": [
"key"
],
"columns": ["key"],
"isUnique": true
}
},
@@ -593,10 +571,7 @@
"compositePrimaryKeys": {
"key_workspace_id_id_pk": {
"name": "key_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -653,10 +628,7 @@
"indexes": {
"model_workspace_model": {
"name": "model_workspace_model",
"columns": [
"workspace_id",
"model"
],
"columns": ["workspace_id", "model"],
"isUnique": true
}
},
@@ -664,10 +636,7 @@
"compositePrimaryKeys": {
"model_workspace_id_id_pk": {
"name": "model_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -731,10 +700,7 @@
"indexes": {
"workspace_provider": {
"name": "workspace_provider",
"columns": [
"workspace_id",
"provider"
],
"columns": ["workspace_id", "provider"],
"isUnique": true
}
},
@@ -742,10 +708,7 @@
"compositePrimaryKeys": {
"provider_workspace_id_id_pk": {
"name": "provider_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -858,32 +821,22 @@
"indexes": {
"user_account_id": {
"name": "user_account_id",
"columns": [
"workspace_id",
"account_id"
],
"columns": ["workspace_id", "account_id"],
"isUnique": true
},
"user_email": {
"name": "user_email",
"columns": [
"workspace_id",
"email"
],
"columns": ["workspace_id", "email"],
"isUnique": true
},
"global_account_id": {
"name": "global_account_id",
"columns": [
"account_id"
],
"columns": ["account_id"],
"isUnique": false
},
"global_email": {
"name": "global_email",
"columns": [
"email"
],
"columns": ["email"],
"isUnique": false
}
},
@@ -891,10 +844,7 @@
"compositePrimaryKeys": {
"user_workspace_id_id_pk": {
"name": "user_workspace_id_id_pk",
"columns": [
"workspace_id",
"id"
]
"columns": ["workspace_id", "id"]
}
},
"uniqueConstraints": {},
@@ -951,9 +901,7 @@
"indexes": {
"slug": {
"name": "slug",
"columns": [
"slug"
],
"columns": ["slug"],
"isUnique": true
}
},
@@ -961,9 +909,7 @@
"compositePrimaryKeys": {
"workspace_id": {
"name": "workspace_id",
"columns": [
"id"
]
"columns": ["id"]
}
},
"uniqueConstraints": {},
@@ -980,4 +926,4 @@
"tables": {},
"indexes": {}
}
}
}
@@ -269,4 +269,4 @@
"breakpoints": true
}
]
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@opencode-ai/console-core",
"version": "1.0.23",
"version": "1.0.39",
"private": true,
"type": "module",
"dependencies": {
+28 -25
View File
@@ -24,37 +24,40 @@ export namespace AWS {
body: z.string(),
}),
async (input) => {
const res = await createClient().fetch("https://email.us-east-1.amazonaws.com/v2/email/outbound-emails", {
method: "POST",
headers: {
"X-Amz-Target": "SES.SendEmail",
"Content-Type": "application/json",
},
body: JSON.stringify({
FromEmailAddress: `OpenCode Zen <contact@anoma.ly>`,
Destination: {
ToAddresses: [input.to],
const res = await createClient().fetch(
"https://email.us-east-1.amazonaws.com/v2/email/outbound-emails",
{
method: "POST",
headers: {
"X-Amz-Target": "SES.SendEmail",
"Content-Type": "application/json",
},
Content: {
Simple: {
Subject: {
Charset: "UTF-8",
Data: input.subject,
},
Body: {
Text: {
body: JSON.stringify({
FromEmailAddress: `OpenCode Zen <contact@anoma.ly>`,
Destination: {
ToAddresses: [input.to],
},
Content: {
Simple: {
Subject: {
Charset: "UTF-8",
Data: input.body,
Data: input.subject,
},
Html: {
Charset: "UTF-8",
Data: input.body,
Body: {
Text: {
Charset: "UTF-8",
Data: input.body,
},
Html: {
Charset: "UTF-8",
Data: input.body,
},
},
},
},
},
}),
})
}),
},
)
if (!res.ok) {
throw new Error(`Failed to send email: ${res.statusText}`)
}
+38 -23
View File
@@ -10,13 +10,12 @@ import { centsToMicroCents } from "./util/price"
import { User } from "./user"
export namespace Billing {
export const CHARGE_NAME = "opencode credits"
export const CHARGE_FEE_NAME = "processing fee"
export const CHARGE_AMOUNT = 2000 // $20
export const CHARGE_AMOUNT_DOLLAR = 20
export const CHARGE_FEE = 123 // Stripe fee 4.4% + $0.30
export const CHARGE_THRESHOLD_DOLLAR = 5
export const CHARGE_THRESHOLD = 500 // $5
export const ITEM_CREDIT_NAME = "opencode credits"
export const ITEM_FEE_NAME = "processing fee"
export const RELOAD_AMOUNT = 20
export const RELOAD_AMOUNT_MIN = 10
export const RELOAD_TRIGGER = 5
export const RELOAD_TRIGGER_MIN = 5
export const stripe = () =>
new Stripe(Resource.STRIPE_SECRET_KEY.value, {
apiVersion: "2025-03-31.basil",
@@ -33,6 +32,8 @@ export namespace Billing {
paymentMethodLast4: BillingTable.paymentMethodLast4,
balance: BillingTable.balance,
reload: BillingTable.reload,
reloadAmount: BillingTable.reloadAmount,
reloadTrigger: BillingTable.reloadTrigger,
monthlyLimit: BillingTable.monthlyLimit,
monthlyUsage: BillingTable.monthlyUsage,
timeMonthlyUsageUpdated: BillingTable.timeMonthlyUsageUpdated,
@@ -67,17 +68,28 @@ export namespace Billing {
)
}
export const calculateFeeInCents = (x: number) => {
// math: x = total - (total * 0.044 + 0.30)
// math: x = total * (1-0.044) - 0.30
// math: (x + 0.30) / 0.956 = total
return Math.round(((x + 30) / 0.956) * 0.044 + 30)
}
export const reload = async () => {
const { customerID, paymentMethodID } = await Database.use((tx) =>
const billing = await Database.use((tx) =>
tx
.select({
customerID: BillingTable.customerID,
paymentMethodID: BillingTable.paymentMethodID,
reloadAmount: BillingTable.reloadAmount,
})
.from(BillingTable)
.where(eq(BillingTable.workspaceID, Actor.workspace()))
.then((rows) => rows[0]),
)
const customerID = billing.customerID
const paymentMethodID = billing.paymentMethodID
const amountInCents = (billing.reloadAmount ?? Billing.RELOAD_AMOUNT) * 100
const paymentID = Identifier.create("payment")
let invoice
try {
@@ -89,18 +101,18 @@ export namespace Billing {
currency: "usd",
})
await Billing.stripe().invoiceItems.create({
amount: Billing.CHARGE_AMOUNT,
amount: amountInCents,
currency: "usd",
customer: customerID!,
description: CHARGE_NAME,
invoice: draft.id!,
description: ITEM_CREDIT_NAME,
})
await Billing.stripe().invoiceItems.create({
amount: Billing.CHARGE_FEE,
amount: calculateFeeInCents(amountInCents),
currency: "usd",
customer: customerID!,
description: CHARGE_FEE_NAME,
invoice: draft.id!,
description: ITEM_FEE_NAME,
})
await Billing.stripe().invoices.finalizeInvoice(draft.id!)
invoice = await Billing.stripe().invoices.pay(draft.id!, {
@@ -128,7 +140,7 @@ export namespace Billing {
await tx
.update(BillingTable)
.set({
balance: sql`${BillingTable.balance} + ${centsToMicroCents(CHARGE_AMOUNT)}`,
balance: sql`${BillingTable.balance} + ${centsToMicroCents(amountInCents)}`,
reloadError: null,
timeReloadError: null,
})
@@ -136,7 +148,7 @@ export namespace Billing {
await tx.insert(PaymentTable).values({
workspaceID: Actor.workspace(),
id: paymentID,
amount: centsToMicroCents(CHARGE_AMOUNT),
amount: centsToMicroCents(amountInCents),
invoiceID: invoice.id!,
paymentID: invoice.payments?.data[0].payment.payment_intent as string,
customerID,
@@ -159,13 +171,19 @@ export namespace Billing {
z.object({
successUrl: z.string(),
cancelUrl: z.string(),
amount: z.number().optional(),
}),
async (input) => {
const user = Actor.assert("user")
const { successUrl, cancelUrl } = input
const { successUrl, cancelUrl, amount } = input
if (amount !== undefined && amount < Billing.RELOAD_AMOUNT_MIN) {
throw new Error(`Amount must be at least $${Billing.RELOAD_AMOUNT_MIN}`)
}
const email = await User.getAuthEmail(user.properties.userID)
const customer = await Billing.get()
const amountInCents = (amount ?? customer.reloadAmount ?? Billing.RELOAD_AMOUNT) * 100
const session = await Billing.stripe().checkout.sessions.create({
mode: "payment",
billing_address_collection: "required",
@@ -173,20 +191,16 @@ export namespace Billing {
{
price_data: {
currency: "usd",
product_data: {
name: CHARGE_NAME,
},
unit_amount: CHARGE_AMOUNT,
product_data: { name: ITEM_CREDIT_NAME },
unit_amount: amountInCents,
},
quantity: 1,
},
{
price_data: {
currency: "usd",
product_data: {
name: CHARGE_FEE_NAME,
},
unit_amount: CHARGE_FEE,
product_data: { name: ITEM_FEE_NAME },
unit_amount: calculateFeeInCents(amountInCents),
},
quantity: 1,
},
@@ -218,6 +232,7 @@ export namespace Billing {
},
metadata: {
workspaceID: Actor.workspace(),
amount: amountInCents.toString(),
},
success_url: successUrl,
cancel_url: cancelUrl,
+8 -2
View File
@@ -5,7 +5,10 @@ import { Client } from "@planetscale/database"
import { MySqlTransaction, type MySqlTransactionConfig } from "drizzle-orm/mysql-core"
import type { ExtractTablesWithRelations } from "drizzle-orm"
import type { PlanetScalePreparedQueryHKT, PlanetscaleQueryResultHKT } from "drizzle-orm/planetscale-serverless"
import type {
PlanetScalePreparedQueryHKT,
PlanetscaleQueryResultHKT,
} from "drizzle-orm/planetscale-serverless"
import { Context } from "../context"
import { memo } from "../util/memo"
@@ -67,7 +70,10 @@ export namespace Database {
}
}
export async function transaction<T>(callback: (tx: TxOrDb) => Promise<T>, config?: MySqlTransactionConfig) {
export async function transaction<T>(
callback: (tx: TxOrDb) => Promise<T>,
config?: MySqlTransactionConfig,
) {
try {
const { tx } = TransactionContext.use()
return callback(tx)
+8 -2
View File
@@ -20,8 +20,14 @@ export namespace Key {
email: AuthTable.subject,
})
.from(KeyTable)
.innerJoin(UserTable, and(eq(KeyTable.userID, UserTable.id), eq(KeyTable.workspaceID, UserTable.workspaceID)))
.innerJoin(AuthTable, and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email")))
.innerJoin(
UserTable,
and(eq(KeyTable.userID, UserTable.id), eq(KeyTable.workspaceID, UserTable.workspaceID)),
)
.innerJoin(
AuthTable,
and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email")),
)
.where(
and(
...[
+9 -2
View File
@@ -11,7 +11,9 @@ export namespace Provider {
tx
.select()
.from(ProviderTable)
.where(and(eq(ProviderTable.workspaceID, Actor.workspace()), isNull(ProviderTable.timeDeleted))),
.where(
and(eq(ProviderTable.workspaceID, Actor.workspace()), isNull(ProviderTable.timeDeleted)),
),
),
)
@@ -50,7 +52,12 @@ export namespace Provider {
return Database.transaction((tx) =>
tx
.delete(ProviderTable)
.where(and(eq(ProviderTable.provider, provider), eq(ProviderTable.workspaceID, Actor.workspace()))),
.where(
and(
eq(ProviderTable.provider, provider),
eq(ProviderTable.workspaceID, Actor.workspace()),
),
),
)
},
)
+8 -1
View File
@@ -1,4 +1,11 @@
import { index, mysqlEnum, mysqlTable, primaryKey, uniqueIndex, varchar } from "drizzle-orm/mysql-core"
import {
index,
mysqlEnum,
mysqlTable,
primaryKey,
uniqueIndex,
varchar,
} from "drizzle-orm/mysql-core"
import { id, timestamps, ulid } from "../drizzle/types"
export const AuthProvider = ["email", "github", "google"] as const
@@ -9,5 +9,8 @@ export const ModelTable = mysqlTable(
...timestamps,
model: varchar("model", { length: 64 }).notNull(),
},
(table) => [...workspaceIndexes(table), uniqueIndex("model_workspace_model").on(table.workspaceID, table.model)],
(table) => [
...workspaceIndexes(table),
uniqueIndex("model_workspace_model").on(table.workspaceID, table.model),
],
)
@@ -10,5 +10,8 @@ export const ProviderTable = mysqlTable(
provider: varchar("provider", { length: 64 }).notNull(),
credentials: text("credentials").notNull(),
},
(table) => [...workspaceIndexes(table), uniqueIndex("workspace_provider").on(table.workspaceID, table.provider)],
(table) => [
...workspaceIndexes(table),
uniqueIndex("workspace_provider").on(table.workspaceID, table.provider),
],
)
+9 -1
View File
@@ -1,4 +1,12 @@
import { mysqlTable, uniqueIndex, varchar, int, mysqlEnum, index, bigint } from "drizzle-orm/mysql-core"
import {
mysqlTable,
uniqueIndex,
varchar,
int,
mysqlEnum,
index,
bigint,
} from "drizzle-orm/mysql-core"
import { timestamps, ulid, utc, workspaceColumns } from "../drizzle/types"
import { workspaceIndexes } from "./workspace.sql"
+23 -5
View File
@@ -26,7 +26,10 @@ export namespace User {
authEmail: AuthTable.subject,
})
.from(UserTable)
.leftJoin(AuthTable, and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email")))
.leftJoin(
AuthTable,
and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email")),
)
.where(and(eq(UserTable.workspaceID, Actor.workspace()), isNull(UserTable.timeDeleted))),
),
)
@@ -36,7 +39,13 @@ export namespace User {
tx
.select()
.from(UserTable)
.where(and(eq(UserTable.workspaceID, Actor.workspace()), eq(UserTable.id, id), isNull(UserTable.timeDeleted)))
.where(
and(
eq(UserTable.workspaceID, Actor.workspace()),
eq(UserTable.id, id),
isNull(UserTable.timeDeleted),
),
)
.then((rows) => rows[0]),
),
)
@@ -48,7 +57,10 @@ export namespace User {
email: AuthTable.subject,
})
.from(UserTable)
.leftJoin(AuthTable, and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email")))
.leftJoin(
AuthTable,
and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email")),
)
.where(and(eq(UserTable.workspaceID, Actor.workspace()), eq(UserTable.id, id)))
.then((rows) => rows[0]?.email),
),
@@ -130,10 +142,16 @@ export namespace User {
workspaceName: WorkspaceTable.name,
})
.from(UserTable)
.innerJoin(AuthTable, and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email")))
.innerJoin(
AuthTable,
and(eq(UserTable.accountID, AuthTable.accountID), eq(AuthTable.provider, "email")),
)
.innerJoin(WorkspaceTable, eq(WorkspaceTable.id, workspaceID))
.where(
and(eq(UserTable.workspaceID, workspaceID), eq(UserTable.id, Actor.assert("user").properties.userID)),
and(
eq(UserTable.workspaceID, workspaceID),
eq(UserTable.id, Actor.assert("user").properties.userID),
),
)
.then((rows) => rows[0]),
)
+69 -69
View File
@@ -6,99 +6,99 @@
import "sst"
declare module "sst" {
export interface Resource {
"ADMIN_SECRET": {
"type": "sst.sst.Secret"
"value": string
ADMIN_SECRET: {
type: "sst.sst.Secret"
value: string
}
"AUTH_API_URL": {
"type": "sst.sst.Linkable"
"value": string
AUTH_API_URL: {
type: "sst.sst.Linkable"
value: string
}
"AWS_SES_ACCESS_KEY_ID": {
"type": "sst.sst.Secret"
"value": string
AWS_SES_ACCESS_KEY_ID: {
type: "sst.sst.Secret"
value: string
}
"AWS_SES_SECRET_ACCESS_KEY": {
"type": "sst.sst.Secret"
"value": string
AWS_SES_SECRET_ACCESS_KEY: {
type: "sst.sst.Secret"
value: string
}
"Console": {
"type": "sst.cloudflare.SolidStart"
"url": string
Console: {
type: "sst.cloudflare.SolidStart"
url: string
}
"Database": {
"database": string
"host": string
"password": string
"port": number
"type": "sst.sst.Linkable"
"username": string
Database: {
database: string
host: string
password: string
port: number
type: "sst.sst.Linkable"
username: string
}
"Desktop": {
"type": "sst.cloudflare.StaticSite"
"url": string
Desktop: {
type: "sst.cloudflare.StaticSite"
url: string
}
"EMAILOCTOPUS_API_KEY": {
"type": "sst.sst.Secret"
"value": string
EMAILOCTOPUS_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_APP_ID": {
"type": "sst.sst.Secret"
"value": string
GITHUB_APP_ID: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_APP_PRIVATE_KEY": {
"type": "sst.sst.Secret"
"value": string
GITHUB_APP_PRIVATE_KEY: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_ID_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_ID_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_SECRET_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_SECRET_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"GOOGLE_CLIENT_ID": {
"type": "sst.sst.Secret"
"value": string
GOOGLE_CLIENT_ID: {
type: "sst.sst.Secret"
value: string
}
"HONEYCOMB_API_KEY": {
"type": "sst.sst.Secret"
"value": string
HONEYCOMB_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_SECRET_KEY": {
"type": "sst.sst.Secret"
"value": string
STRIPE_SECRET_KEY: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_WEBHOOK_SECRET": {
"type": "sst.sst.Linkable"
"value": string
STRIPE_WEBHOOK_SECRET: {
type: "sst.sst.Linkable"
value: string
}
"Web": {
"type": "sst.cloudflare.Astro"
"url": string
Web: {
type: "sst.cloudflare.Astro"
url: string
}
"ZEN_MODELS1": {
"type": "sst.sst.Secret"
"value": string
ZEN_MODELS1: {
type: "sst.sst.Secret"
value: string
}
"ZEN_MODELS2": {
"type": "sst.sst.Secret"
"value": string
ZEN_MODELS2: {
type: "sst.sst.Secret"
value: string
}
}
}
// cloudflare
import * as cloudflare from "@cloudflare/workers-types";
// cloudflare
import * as cloudflare from "@cloudflare/workers-types"
declare module "sst" {
export interface Resource {
"Api": cloudflare.Service
"AuthApi": cloudflare.Service
"AuthStorage": cloudflare.KVNamespace
"Bucket": cloudflare.R2Bucket
"LogProcessor": cloudflare.Service
Api: cloudflare.Service
AuthApi: cloudflare.Service
AuthStorage: cloudflare.KVNamespace
Bucket: cloudflare.R2Bucket
LogProcessor: cloudflare.Service
}
}
import "sst"
export {}
export {}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-function",
"version": "1.0.23",
"version": "1.0.39",
"$schema": "https://json.schemastore.org/package.json",
"private": true,
"type": "module",
+69 -69
View File
@@ -6,99 +6,99 @@
import "sst"
declare module "sst" {
export interface Resource {
"ADMIN_SECRET": {
"type": "sst.sst.Secret"
"value": string
ADMIN_SECRET: {
type: "sst.sst.Secret"
value: string
}
"AUTH_API_URL": {
"type": "sst.sst.Linkable"
"value": string
AUTH_API_URL: {
type: "sst.sst.Linkable"
value: string
}
"AWS_SES_ACCESS_KEY_ID": {
"type": "sst.sst.Secret"
"value": string
AWS_SES_ACCESS_KEY_ID: {
type: "sst.sst.Secret"
value: string
}
"AWS_SES_SECRET_ACCESS_KEY": {
"type": "sst.sst.Secret"
"value": string
AWS_SES_SECRET_ACCESS_KEY: {
type: "sst.sst.Secret"
value: string
}
"Console": {
"type": "sst.cloudflare.SolidStart"
"url": string
Console: {
type: "sst.cloudflare.SolidStart"
url: string
}
"Database": {
"database": string
"host": string
"password": string
"port": number
"type": "sst.sst.Linkable"
"username": string
Database: {
database: string
host: string
password: string
port: number
type: "sst.sst.Linkable"
username: string
}
"Desktop": {
"type": "sst.cloudflare.StaticSite"
"url": string
Desktop: {
type: "sst.cloudflare.StaticSite"
url: string
}
"EMAILOCTOPUS_API_KEY": {
"type": "sst.sst.Secret"
"value": string
EMAILOCTOPUS_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_APP_ID": {
"type": "sst.sst.Secret"
"value": string
GITHUB_APP_ID: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_APP_PRIVATE_KEY": {
"type": "sst.sst.Secret"
"value": string
GITHUB_APP_PRIVATE_KEY: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_ID_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_ID_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_SECRET_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_SECRET_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"GOOGLE_CLIENT_ID": {
"type": "sst.sst.Secret"
"value": string
GOOGLE_CLIENT_ID: {
type: "sst.sst.Secret"
value: string
}
"HONEYCOMB_API_KEY": {
"type": "sst.sst.Secret"
"value": string
HONEYCOMB_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_SECRET_KEY": {
"type": "sst.sst.Secret"
"value": string
STRIPE_SECRET_KEY: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_WEBHOOK_SECRET": {
"type": "sst.sst.Linkable"
"value": string
STRIPE_WEBHOOK_SECRET: {
type: "sst.sst.Linkable"
value: string
}
"Web": {
"type": "sst.cloudflare.Astro"
"url": string
Web: {
type: "sst.cloudflare.Astro"
url: string
}
"ZEN_MODELS1": {
"type": "sst.sst.Secret"
"value": string
ZEN_MODELS1: {
type: "sst.sst.Secret"
value: string
}
"ZEN_MODELS2": {
"type": "sst.sst.Secret"
"value": string
ZEN_MODELS2: {
type: "sst.sst.Secret"
value: string
}
}
}
// cloudflare
import * as cloudflare from "@cloudflare/workers-types";
// cloudflare
import * as cloudflare from "@cloudflare/workers-types"
declare module "sst" {
export interface Resource {
"Api": cloudflare.Service
"AuthApi": cloudflare.Service
"AuthStorage": cloudflare.KVNamespace
"Bucket": cloudflare.R2Bucket
"LogProcessor": cloudflare.Service
Api: cloudflare.Service
AuthApi: cloudflare.Service
AuthStorage: cloudflare.KVNamespace
Bucket: cloudflare.R2Bucket
LogProcessor: cloudflare.Service
}
}
import "sst"
export {}
export {}
@@ -1,6 +1,18 @@
// @ts-nocheck
import React from "react"
import { Img, Row, Html, Link, Body, Head, Button, Column, Preview, Section, Container } from "@jsx-email/all"
import {
Img,
Row,
Html,
Link,
Body,
Head,
Button,
Column,
Preview,
Section,
Container,
} from "@jsx-email/all"
import { Text, Fonts, Title, A, Span } from "../components"
import {
unit,
@@ -52,8 +64,8 @@ export const InviteEmail = ({
<Section style={{ padding: `${unit * 2}px 0 0 0` }}>
<Text style={headingText}>Join your team's OpenCode workspace</Text>
<Text style={contentText}>
You have been invited by <Span style={contentHighlightText}>{inviter}</Span> to join the{" "}
<Span style={contentHighlightText}>{workspaceName}</Span> workspace on OpenCode.
You have been invited by <Span style={contentHighlightText}>{inviter}</Span> to join
the <Span style={contentHighlightText}>{workspaceName}</Span> workspace on OpenCode.
</Text>
</Section>
@@ -61,7 +73,12 @@ export const InviteEmail = ({
<Button style={button} href={url}>
<Text style={buttonText}>
Join workspace
<Img width="24" height="24" src={`${assetsUrl}/right-arrow.png`} alt="Arrow right" />
<Img
width="24"
height="24"
src={`${assetsUrl}/right-arrow.png`}
alt="Arrow right"
/>
</Text>
</Button>
</Section>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/console-mail",
"version": "1.0.23",
"version": "1.0.39",
"dependencies": {
"@jsx-email/all": "2.2.3",
"@jsx-email/cli": "1.4.3",
+1 -1
View File
@@ -6,4 +6,4 @@
/// <reference path="../../../sst-env.d.ts" />
import "sst"
export {}
export {}
+69 -69
View File
@@ -6,99 +6,99 @@
import "sst"
declare module "sst" {
export interface Resource {
"ADMIN_SECRET": {
"type": "sst.sst.Secret"
"value": string
ADMIN_SECRET: {
type: "sst.sst.Secret"
value: string
}
"AUTH_API_URL": {
"type": "sst.sst.Linkable"
"value": string
AUTH_API_URL: {
type: "sst.sst.Linkable"
value: string
}
"AWS_SES_ACCESS_KEY_ID": {
"type": "sst.sst.Secret"
"value": string
AWS_SES_ACCESS_KEY_ID: {
type: "sst.sst.Secret"
value: string
}
"AWS_SES_SECRET_ACCESS_KEY": {
"type": "sst.sst.Secret"
"value": string
AWS_SES_SECRET_ACCESS_KEY: {
type: "sst.sst.Secret"
value: string
}
"Console": {
"type": "sst.cloudflare.SolidStart"
"url": string
Console: {
type: "sst.cloudflare.SolidStart"
url: string
}
"Database": {
"database": string
"host": string
"password": string
"port": number
"type": "sst.sst.Linkable"
"username": string
Database: {
database: string
host: string
password: string
port: number
type: "sst.sst.Linkable"
username: string
}
"Desktop": {
"type": "sst.cloudflare.StaticSite"
"url": string
Desktop: {
type: "sst.cloudflare.StaticSite"
url: string
}
"EMAILOCTOPUS_API_KEY": {
"type": "sst.sst.Secret"
"value": string
EMAILOCTOPUS_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_APP_ID": {
"type": "sst.sst.Secret"
"value": string
GITHUB_APP_ID: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_APP_PRIVATE_KEY": {
"type": "sst.sst.Secret"
"value": string
GITHUB_APP_PRIVATE_KEY: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_ID_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_ID_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"GITHUB_CLIENT_SECRET_CONSOLE": {
"type": "sst.sst.Secret"
"value": string
GITHUB_CLIENT_SECRET_CONSOLE: {
type: "sst.sst.Secret"
value: string
}
"GOOGLE_CLIENT_ID": {
"type": "sst.sst.Secret"
"value": string
GOOGLE_CLIENT_ID: {
type: "sst.sst.Secret"
value: string
}
"HONEYCOMB_API_KEY": {
"type": "sst.sst.Secret"
"value": string
HONEYCOMB_API_KEY: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_SECRET_KEY": {
"type": "sst.sst.Secret"
"value": string
STRIPE_SECRET_KEY: {
type: "sst.sst.Secret"
value: string
}
"STRIPE_WEBHOOK_SECRET": {
"type": "sst.sst.Linkable"
"value": string
STRIPE_WEBHOOK_SECRET: {
type: "sst.sst.Linkable"
value: string
}
"Web": {
"type": "sst.cloudflare.Astro"
"url": string
Web: {
type: "sst.cloudflare.Astro"
url: string
}
"ZEN_MODELS1": {
"type": "sst.sst.Secret"
"value": string
ZEN_MODELS1: {
type: "sst.sst.Secret"
value: string
}
"ZEN_MODELS2": {
"type": "sst.sst.Secret"
"value": string
ZEN_MODELS2: {
type: "sst.sst.Secret"
value: string
}
}
}
// cloudflare
import * as cloudflare from "@cloudflare/workers-types";
// cloudflare
import * as cloudflare from "@cloudflare/workers-types"
declare module "sst" {
export interface Resource {
"Api": cloudflare.Service
"AuthApi": cloudflare.Service
"AuthStorage": cloudflare.KVNamespace
"Bucket": cloudflare.R2Bucket
"LogProcessor": cloudflare.Service
Api: cloudflare.Service
AuthApi: cloudflare.Service
AuthStorage: cloudflare.KVNamespace
Bucket: cloudflare.R2Bucket
LogProcessor: cloudflare.Service
}
}
import "sst"
export {}
export {}
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@opencode-ai/desktop",
"version": "1.0.23",
"version": "1.0.39",
"description": "",
"type": "module",
"scripts": {
@@ -31,6 +31,7 @@
"@solid-primitives/event-bus": "1.1.2",
"@solid-primitives/resize-observer": "2.1.3",
"@solid-primitives/scroll": "2.1.3",
"@solid-primitives/storage": "4.3.3",
"@solidjs/meta": "catalog:",
"@solidjs/router": "0.15.3",
"@thisbeyond/solid-dnd": "0.7.5",
+6 -6
View File
@@ -99,13 +99,13 @@ export function Code(props: Props) {
})
}
const onSelectionChange = () => {
const onSelectionChange = async () => {
if (!container) return
if (isProgrammaticSelection) return
// if (ctx.file.active()?.path !== local.path) return
const d = getSelectionInContainer(container)
if (!d) return
const p = ctx.file.node(local.path)?.selection
const p = (await ctx.file.node(local.path))?.selection
if (p && p.startLine === d.sl && p.endLine === d.el && p.startChar === d.sch && p.endChar === d.ech) return
ctx.file.select(local.path, { startLine: d.sl, startChar: d.sch, endLine: d.el, endChar: d.ech })
}
@@ -144,21 +144,21 @@ export function Code(props: Props) {
})
// Restore scroll position from store when content is ready
createEffect(() => {
createEffect(async () => {
const content = html()
if (!container || !content) return
const top = ctx.file.node(local.path)?.scrollTop
const top = (await ctx.file.node(local.path))?.scrollTop
if (top !== undefined && container.scrollTop !== top) container.scrollTop = top
})
// Sync selection from store -> DOM
createEffect(() => {
createEffect(async () => {
const content = html()
if (!container || !content) return
// if (ctx.file.active()?.path !== local.path) return
const codeEl = container.querySelector("code") as HTMLElement | undefined
if (!codeEl) return
const target = ctx.file.node(local.path)?.selection
const target = (await ctx.file.node(local.path))?.selection
const current = getSelectionInContainer(container)
const sel = window.getSelection()
if (!sel) return
@@ -77,7 +77,7 @@ export default function FileTree(props: {
<Collapsible
class="w-full"
forceMount={false}
open={local.file.node(node.path)?.expanded}
// open={local.file.node(node.path)?.expanded}
onOpenChange={(open) => (open ? local.file.expand(node.path) : local.file.collapse(node.path))}
>
<Collapsible.Trigger>
@@ -85,7 +85,7 @@ export default function FileTree(props: {
<Collapsible.Arrow class="text-text-muted/60 ml-1" />
<FileIcon
node={node}
expanded={local.file.node(node.path).expanded}
// expanded={local.file.node(node.path).expanded}
class="text-text-muted/60 -ml-1"
/>
</Node>
@@ -70,9 +70,8 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
const lastPart = createMemo(() => resolvedParts().slice(-1)?.at(0))
const rawStatus = createMemo(() => {
const defaultStatus = "Working..."
const last = lastPart()
if (!last) return defaultStatus
if (!last) return undefined
if (last.type === "tool") {
switch (last.tool) {
@@ -102,7 +101,7 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
} else if (last.type === "text") {
return "Gathering thoughts..."
}
return defaultStatus
return undefined
})
const [status, setStatus] = createSignal(rawStatus())
@@ -111,11 +110,11 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
createEffect(() => {
const newStatus = rawStatus()
if (newStatus === status()) return
if (newStatus === status() || !newStatus) return
const timeSinceLastChange = Date.now() - lastStatusChange
if (timeSinceLastChange >= 1000) {
if (timeSinceLastChange >= 1500) {
setStatus(newStatus)
lastStatusChange = Date.now()
if (statusTimeout) {
@@ -145,7 +144,7 @@ export function MessageProgress(props: { assistantMessages: () => AssistantMessa
{/* )} */}
{/* </Show> */}
<div class="flex items-center gap-x-5 pl-3 border border-transparent text-text-base">
<Spinner /> <span class="text-12-medium">{status()}</span>
<Spinner /> <span class="text-12-medium">{status() ?? "Considering next steps..."}</span>
</div>
<Show when={eligibleItems().length > 0}>
<div
+179 -68
View File
@@ -1,51 +1,41 @@
import { Button, Icon, IconButton, Select, SelectDialog } from "@opencode-ai/ui"
import { Button, Icon, IconButton, Select, SelectDialog, Tooltip } from "@opencode-ai/ui"
import { useFilteredList } from "@opencode-ai/ui/hooks"
import { createEffect, on, Component, createMemo, Show, For, onMount, onCleanup } from "solid-js"
import { createEffect, on, Component, Show, For, onMount, onCleanup, Switch, Match } from "solid-js"
import { createStore } from "solid-js/store"
import { FileIcon } from "@/ui"
import { getDirectory, getFilename } from "@/utils"
import { createFocusSignal } from "@solid-primitives/active-element"
import { TextSelection, useLocal } from "@/context/local"
import { useLocal } from "@/context/local"
import { DateTime } from "luxon"
interface PartBase {
content: string
start: number
end: number
}
export interface TextPart extends PartBase {
type: "text"
}
export interface FileAttachmentPart extends PartBase {
type: "file"
path: string
selection?: TextSelection
}
export type ContentPart = TextPart | FileAttachmentPart
import { ContentPart, DEFAULT_PROMPT, isPromptEqual, Prompt, useSession } from "@/context/session"
import { useSDK } from "@/context/sdk"
import { useNavigate } from "@solidjs/router"
import { useSync } from "@/context/sync"
interface PromptInputProps {
onSubmit: (parts: ContentPart[]) => void
class?: string
ref?: (el: HTMLDivElement) => void
}
export const PromptInput: Component<PromptInputProps> = (props) => {
const navigate = useNavigate()
const sdk = useSDK()
const sync = useSync()
const local = useLocal()
const session = useSession()
let editorRef!: HTMLDivElement
const defaultParts = [{ type: "text", content: "", start: 0, end: 0 } as const]
const [store, setStore] = createStore<{
contentParts: ContentPart[]
popoverIsOpen: boolean
}>({
contentParts: defaultParts,
popoverIsOpen: false,
})
const isEmpty = createMemo(() => isEqual(store.contentParts, defaultParts))
createEffect(() => {
session.id
editorRef.focus()
})
const isFocused = createFocusSignal(() => editorRef)
const handlePaste = (event: ClipboardEvent) => {
@@ -71,14 +61,16 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}
})
const handleFileSelect = (path: string | undefined) => {
if (!path) return
addPart({ type: "file", path, content: "@" + getFilename(path), start: 0, end: 0 })
setStore("popoverIsOpen", false)
}
const { flat, active, onInput, onKeyDown, refetch } = useFilteredList<string>({
items: local.file.searchFilesAndDirectories,
key: (x) => x,
onSelect: (path) => {
if (!path) return
addPart({ type: "file", path, content: "@" + getFilename(path), start: 0, end: 0 })
setStore("popoverIsOpen", false)
},
onSelect: handleFileSelect,
})
createEffect(() => {
@@ -88,10 +80,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
createEffect(
on(
() => store.contentParts,
() => session.prompt.current(),
(currentParts) => {
const domParts = parseFromDOM()
if (isEqual(currentParts, domParts)) return
if (isPromptEqual(currentParts, domParts)) return
const selection = window.getSelection()
let cursorPosition: number | null = null
@@ -122,8 +114,18 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
),
)
const parseFromDOM = (): ContentPart[] => {
const newParts: ContentPart[] = []
createEffect(
on(
() => session.prompt.cursor(),
(cursor) => {
if (cursor === undefined) return
queueMicrotask(() => setCursorPosition(editorRef, cursor))
},
),
)
const parseFromDOM = (): Prompt => {
const newParts: Prompt = []
let position = 0
editorRef.childNodes.forEach((node) => {
if (node.nodeType === Node.TEXT_NODE) {
@@ -150,7 +152,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}
}
})
if (newParts.length === 0) newParts.push(...defaultParts)
if (newParts.length === 0) newParts.push(...DEFAULT_PROMPT)
return newParts
}
@@ -167,12 +169,15 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
setStore("popoverIsOpen", false)
}
setStore("contentParts", rawParts)
session.prompt.set(rawParts, cursorPosition)
}
const addPart = (part: ContentPart) => {
const cursorPosition = getCursorPosition(editorRef)
const rawText = store.contentParts.map((p) => p.content).join("")
const rawText = session.prompt
.current()
.map((p) => p.content)
.join("")
const textBeforeCursor = rawText.substring(0, cursorPosition)
const atMatch = textBeforeCursor.match(/@(\S*)$/)
@@ -198,7 +203,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
parts: nextParts,
inserted,
cursorPositionAfter,
} = store.contentParts.reduce(
} = session.prompt.current().reduce(
(acc, item) => {
if (acc.inserted) {
acc.parts.push({ ...item, start: acc.runningIndex, end: acc.runningIndex + item.content.length })
@@ -257,7 +262,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
)
if (!inserted) {
const baseParts = store.contentParts.filter((item) => !(item.type === "text" && item.content === ""))
const baseParts = session.prompt.current().filter((item) => !(item.type === "text" && item.content === ""))
const runningIndex = baseParts.reduce((sum, p) => sum + p.content.length, 0)
const appendedAcc = { parts: [...baseParts] as ContentPart[], runningIndex }
if (part.type === "text") {
@@ -270,20 +275,27 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
end: appendedAcc.runningIndex + part.content.length,
})
}
const next = appendedAcc.parts.length > 0 ? appendedAcc.parts : defaultParts
setStore("contentParts", next)
setStore("popoverIsOpen", false)
const next = appendedAcc.parts.length > 0 ? appendedAcc.parts : DEFAULT_PROMPT
const nextCursor = rawText.length + part.content.length
session.prompt.set(next, nextCursor)
setStore("popoverIsOpen", false)
queueMicrotask(() => setCursorPosition(editorRef, nextCursor))
return
}
setStore("contentParts", nextParts)
session.prompt.set(nextParts, cursorPositionAfter)
setStore("popoverIsOpen", false)
queueMicrotask(() => setCursorPosition(editorRef, cursorPositionAfter))
}
const abort = () =>
sdk.client.session.abort({
path: {
id: session.id!,
},
})
const handleKeyDown = (event: KeyboardEvent) => {
if (store.popoverIsOpen && (event.key === "ArrowUp" || event.key === "ArrowDown" || event.key === "Enter")) {
onKeyDown(event)
@@ -293,14 +305,102 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (event.key === "Enter" && !event.shiftKey) {
handleSubmit(event)
}
if (event.key === "Escape") {
if (store.popoverIsOpen) {
setStore("popoverIsOpen", false)
} else if (session.working()) {
abort()
}
}
}
const handleSubmit = (event: Event) => {
const handleSubmit = async (event: Event) => {
event.preventDefault()
if (store.contentParts.length > 0) {
props.onSubmit([...store.contentParts])
setStore("contentParts", defaultParts)
const text = session.prompt
.current()
.map((part) => part.content)
.join("")
if (text.trim().length === 0) {
if (session.working()) abort()
return
}
let existing = session.info()
if (!existing) {
const created = await sdk.client.session.create()
existing = created.data ?? undefined
}
if (!existing) return
navigate(`/session/${existing.id}`)
if (!session.id) {
// session.layout.setOpenedTabs(
// session.layout.copyTabs("", session.id)
}
session.layout.setActiveTab(undefined)
session.messages.setActive(undefined)
const toAbsolutePath = (path: string) => (path.startsWith("/") ? path : sync.absolute(path))
const attachments = session.prompt.current().filter((part) => part.type === "file")
// const activeFile = local.context.active()
// if (activeFile) {
// registerAttachment(
// activeFile.path,
// activeFile.selection,
// activeFile.name ?? formatAttachmentLabel(activeFile.path, activeFile.selection),
// )
// }
// for (const contextFile of local.context.all()) {
// registerAttachment(
// contextFile.path,
// contextFile.selection,
// formatAttachmentLabel(contextFile.path, contextFile.selection),
// )
// }
const attachmentParts = attachments.map((attachment) => {
const absolute = toAbsolutePath(attachment.path)
const query = attachment.selection
? `?start=${attachment.selection.startLine}&end=${attachment.selection.endLine}`
: ""
return {
type: "file" as const,
mime: "text/plain",
url: `file://${absolute}${query}`,
filename: getFilename(attachment.path),
source: {
type: "file" as const,
text: {
value: attachment.content,
start: attachment.start,
end: attachment.end,
},
path: absolute,
},
}
})
session.prompt.set(DEFAULT_PROMPT, 0)
await sdk.client.session.prompt({
path: { id: existing.id },
body: {
agent: local.agent.current()!.name,
model: {
modelID: local.model.current()!.id,
providerID: local.model.current()!.provider.id,
},
parts: [
{
type: "text",
text,
},
...attachmentParts,
],
},
})
}
return (
@@ -310,11 +410,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<Show when={flat().length > 0} fallback={<div class="text-text-weak px-2">No matching files</div>}>
<For each={flat()}>
{(i) => (
<div
<button
classList={{
"w-full flex items-center justify-between rounded-md": true,
"bg-surface-raised-base-hover": active() === i,
}}
onClick={() => handleFileSelect(i)}
>
<div class="flex items-center gap-x-2 grow min-w-0">
<FileIcon node={{ path: i, type: "file" }} class="shrink-0 size-4" />
@@ -326,7 +427,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
</div>
</div>
<div class="flex items-center gap-x-1 text-text-muted/40 shrink-0"></div>
</div>
</button>
)}
</For>
</Show>
@@ -354,7 +455,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
"[&>[data-type=file]]:text-icon-info-active": true,
}}
/>
<Show when={isEmpty()}>
<Show when={!session.prompt.dirty()}>
<div class="absolute top-0 left-0 p-3 text-14-regular text-text-weak pointer-events-none">
Plan and build anything
</div>
@@ -419,29 +520,39 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
)}
</SelectDialog>
</div>
<IconButton type="submit" disabled={isEmpty()} icon="arrow-up" variant="primary" />
<Tooltip
placement="top"
value={
<Switch>
<Match when={session.working()}>
<div class="flex items-center gap-2">
<span>Stop</span>
<span class="text-icon-base text-12-medium text-[10px]!">ESC</span>
</div>
</Match>
<Match when={true}>
<div class="flex items-center gap-2">
<span>Send</span>
<Icon name="enter" size="small" class="text-icon-base" />
</div>
</Match>
</Switch>
}
>
<IconButton
type="submit"
disabled={!session.prompt.dirty() && !session.working()}
icon={session.working() ? "stop" : "arrow-up"}
variant="primary"
class="rounded-full"
/>
</Tooltip>
</div>
</form>
</div>
)
}
function isEqual(arrA: ContentPart[], arrB: ContentPart[]): boolean {
if (arrA.length !== arrB.length) return false
for (let i = 0; i < arrA.length; i++) {
const partA = arrA[i]
const partB = arrB[i]
if (partA.type !== partB.type) return false
if (partA.type === "text" && partA.content !== (partB as TextPart).content) {
return false
}
if (partA.type === "file" && partA.path !== (partB as FileAttachmentPart).path) {
return false
}
}
return true
}
function getCursorPosition(parent: HTMLElement): number {
const selection = window.getSelection()
if (!selection || selection.rangeCount === 0) return 0
+71 -155
View File
@@ -5,6 +5,7 @@ import type { FileContent, FileNode, Model, Provider, File as FileStatus } from
import { createSimpleContext } from "./helper"
import { useSDK } from "./sdk"
import { useSync } from "./sync"
import { makePersisted } from "@solid-primitives/storage"
export type LocalFile = FileNode &
Partial<{
@@ -195,18 +196,10 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
const file = (() => {
const [store, setStore] = createStore<{
node: Record<string, LocalFile>
// opened: string[]
// active?: string
}>({
node: Object.fromEntries(sync.data.node.map((x) => [x.path, x])),
// opened: [],
})
// const active = createMemo(() => {
// if (!store.active) return undefined
// return store.node[store.active]
// })
// const opened = createMemo(() => store.opened.map((x) => store.node[x]))
const changeset = createMemo(() => new Set(sync.data.changes.map((f) => f.path)))
const changes = createMemo(() => Array.from(changeset()).sort((a, b) => a.localeCompare(b)))
@@ -247,18 +240,18 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
return false
}
const resetNode = (path: string) => {
setStore("node", path, {
loaded: undefined,
pinned: undefined,
content: undefined,
selection: undefined,
scrollTop: undefined,
folded: undefined,
view: undefined,
selectedChange: undefined,
})
}
// const resetNode = (path: string) => {
// setStore("node", path, {
// loaded: undefined,
// pinned: undefined,
// content: undefined,
// selection: undefined,
// scrollTop: undefined,
// folded: undefined,
// view: undefined,
// selectedChange: undefined,
// })
// }
const relative = (path: string) => path.replace(sync.data.path.directory + "/", "")
@@ -333,31 +326,21 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
sdk.event.listen((e) => {
const event = e.details
switch (event.type) {
case "message.part.updated":
const part = event.properties.part
if (part.type === "tool" && part.state.status === "completed") {
switch (part.tool) {
case "read":
break
case "edit":
// load(part.state.input["filePath"] as string)
break
default:
break
}
}
break
case "file.watcher.updated":
// setTimeout(sync.load.changes, 1000)
// const relativePath = relative(event.properties.file)
// if (relativePath.startsWith(".git/")) return
// load(relativePath)
const relativePath = relative(event.properties.file)
if (relativePath.startsWith(".git/")) return
load(relativePath)
break
}
})
return {
node: (path: string) => store.node[path],
node: async (path: string) => {
if (!store.node[path]) {
await init(path)
}
return store.node[path]
},
update: (path: string, node: LocalFile) => setStore("node", path, reconcile(node)),
open,
load,
@@ -417,121 +400,6 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
searchFiles,
searchFilesAndDirectories,
relative,
// active,
// opened,
// close(path: string) {
// setStore("opened", (opened) => opened.filter((x) => x !== path))
// if (store.active === path) {
// const index = store.opened.findIndex((f) => f === path)
// const previous = store.opened[Math.max(0, index - 1)]
// setStore("active", previous)
// }
// resetNode(path)
// },
// move(path: string, to: number) {
// const index = store.opened.findIndex((f) => f === path)
// if (index === -1) return
// setStore(
// "opened",
// produce((opened) => {
// opened.splice(to, 0, opened.splice(index, 1)[0])
// }),
// )
// setStore("node", path, "pinned", true)
// },
}
})()
const session = (() => {
const [store, setStore] = createStore<{
active?: string
tabs: Record<
string,
{
active?: string
opened: string[]
}
>
}>({
tabs: {
"": {
opened: [],
},
},
})
const active = createMemo(() => {
if (!store.active) return undefined
return sync.session.get(store.active)
})
createEffect(() => {
if (!store.active) return
sync.session.sync(store.active)
if (!store.tabs[store.active]) {
setStore("tabs", store.active, {
opened: [],
})
}
})
const tabs = createMemo(() => store.tabs[store.active ?? ""])
return {
active,
setActive(sessionId: string | undefined) {
setStore("active", sessionId)
},
clearActive() {
setStore("active", undefined)
},
tabs,
copyTabs(from: string, to: string) {
setStore("tabs", to, {
opened: store.tabs[from]?.opened ?? [],
})
},
setActiveTab(tab: string | undefined) {
setStore("tabs", store.active ?? "", "active", tab)
},
async open(tab: string) {
if (tab !== "chat") {
await file.open(tab)
}
if (!tabs()?.opened?.includes(tab)) {
setStore("tabs", store.active ?? "", "opened", [...(tabs()?.opened ?? []), tab])
}
setStore("tabs", store.active ?? "", "active", tab)
},
close(tab: string) {
batch(() => {
if (!tabs()) return
setStore("tabs", store.active ?? "", {
active: tabs()!.active,
opened: tabs()!.opened.filter((x) => x !== tab),
})
if (tabs()!.active === tab) {
const index = tabs()!.opened.findIndex((f) => f === tab)
const previous = tabs()!.opened[Math.max(0, index - 1)]
setStore("tabs", store.active ?? "", "active", previous)
}
})
},
move(tab: string, to: number) {
if (!tabs()) return
const index = tabs()!.opened.findIndex((f) => f === tab)
if (index === -1) return
setStore(
"tabs",
store.active ?? "",
"opened",
produce((opened) => {
opened.splice(to, 0, opened.splice(index, 1)[0])
}),
)
// setStore("node", path, "pinned", true)
},
}
})()
@@ -589,12 +457,60 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
}
})()
const layout = (() => {
const [store, setStore] = makePersisted(
createStore({
sidebar: {
opened: true,
width: 240,
},
review: {
state: "closed" as "open" | "closed" | "tab",
},
}),
{
name: "default-layout",
},
)
return {
sidebar: {
opened: createMemo(() => store.sidebar.opened),
open() {
setStore("sidebar", "opened", true)
},
close() {
setStore("sidebar", "opened", false)
},
toggle() {
setStore("sidebar", "opened", (x) => !x)
},
width: createMemo(() => store.sidebar.width),
resize(width: number) {
setStore("sidebar", "width", width)
},
},
review: {
state: createMemo(() => store.review?.state ?? "closed"),
open() {
setStore("review", "state", "open")
},
close() {
setStore("review", "state", "closed")
},
tab() {
setStore("review", "state", "tab")
},
},
}
})()
const result = {
model,
agent,
file,
session,
context,
layout,
}
return result
},
+217
View File
@@ -0,0 +1,217 @@
import { createStore, produce } from "solid-js/store"
import { createSimpleContext } from "./helper"
import { batch, createEffect, createMemo } from "solid-js"
import { useSync } from "./sync"
import { makePersisted } from "@solid-primitives/storage"
import { TextSelection, useLocal } from "./local"
import { pipe, sumBy } from "remeda"
import { AssistantMessage } from "@opencode-ai/sdk"
export const { use: useSession, provider: SessionProvider } = createSimpleContext({
name: "Session",
init: (props: { sessionId?: string }) => {
const sync = useSync()
const local = useLocal()
const [store, setStore] = makePersisted(
createStore<{
prompt: Prompt
cursorPosition?: number
messageId?: string
tabs: {
active?: string
opened: string[]
}
}>({
prompt: [{ type: "text", content: "", start: 0, end: 0 }],
tabs: {
opened: [],
},
}),
{
name: props.sessionId ?? "new-session",
},
)
createEffect(() => {
if (!props.sessionId) return
sync.session.sync(props.sessionId)
})
const info = createMemo(() => (props.sessionId ? sync.session.get(props.sessionId) : undefined))
const messages = createMemo(() => (props.sessionId ? (sync.data.message[props.sessionId] ?? []) : []))
const userMessages = createMemo(() =>
messages()
.filter((m) => m.role === "user")
.sort((a, b) => b.id.localeCompare(a.id)),
)
const lastUserMessage = createMemo(() => {
return userMessages()?.at(0)
})
const activeMessage = createMemo(() => {
if (!store.messageId) return lastUserMessage()
return userMessages()?.find((m) => m.id === store.messageId)
})
const working = createMemo(() => {
if (!props.sessionId) return false
const last = lastUserMessage()
if (!last) return false
const assistantMessages = sync.data.message[props.sessionId]?.filter(
(m) => m.role === "assistant" && m.parentID == last?.id,
) as AssistantMessage[]
const error = assistantMessages?.find((m) => m?.error)?.error
return !last?.summary?.body && !error
})
const cost = createMemo(() => {
const total = pipe(
messages(),
sumBy((x) => (x.role === "assistant" ? x.cost : 0)),
)
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(total)
})
const last = createMemo(
() => messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage,
)
const model = createMemo(() =>
last() ? sync.data.provider.find((x) => x.id === last().providerID)?.models[last().modelID] : undefined,
)
const diffs = createMemo(() => (props.sessionId ? (sync.data.session_diff[props.sessionId] ?? []) : []))
const tokens = createMemo(() => {
if (!last()) return
const tokens = last().tokens
return tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write
})
const context = createMemo(() => {
const total = tokens()
const limit = model()?.limit.context
if (!total || !limit) return 0
return Math.round((total / limit) * 100)
})
return {
id: props.sessionId,
info,
working,
diffs,
prompt: {
current: createMemo(() => store.prompt),
cursor: createMemo(() => store.cursorPosition),
dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)),
set(prompt: Prompt, cursorPosition?: number) {
batch(() => {
setStore("prompt", prompt)
if (cursorPosition !== undefined) setStore("cursorPosition", cursorPosition)
})
},
},
messages: {
all: messages,
user: userMessages,
last: lastUserMessage,
active: activeMessage,
setActive(id: string | undefined) {
setStore("messageId", id)
},
},
usage: {
tokens,
cost,
context,
},
layout: {
tabs: store.tabs,
setActiveTab(tab: string | undefined) {
setStore("tabs", "active", tab)
},
setOpenedTabs(tabs: string[]) {
setStore("tabs", "opened", tabs)
},
async openTab(tab: string) {
if (tab === "chat") {
setStore("tabs", "active", undefined)
return
}
if (tab.startsWith("file://")) {
await local.file.open(tab.replace("file://", ""))
}
if (tab !== "review") {
if (!store.tabs.opened.includes(tab)) {
setStore("tabs", "opened", [...store.tabs.opened, tab])
}
}
setStore("tabs", "active", tab)
},
closeTab(tab: string) {
batch(() => {
setStore(
"tabs",
"opened",
store.tabs.opened.filter((x) => x !== tab),
)
if (store.tabs.active === tab) {
const index = store.tabs.opened.findIndex((f) => f === tab)
const previous = store.tabs.opened[Math.max(0, index - 1)]
setStore("tabs", "active", previous)
}
})
},
moveTab(tab: string, to: number) {
const index = store.tabs.opened.findIndex((f) => f === tab)
if (index === -1) return
setStore(
"tabs",
"opened",
produce((opened) => {
opened.splice(to, 0, opened.splice(index, 1)[0])
}),
)
// setStore("node", path, "pinned", true)
},
},
}
},
})
interface PartBase {
content: string
start: number
end: number
}
export interface TextPart extends PartBase {
type: "text"
}
export interface FileAttachmentPart extends PartBase {
type: "file"
path: string
selection?: TextSelection
}
export type ContentPart = TextPart | FileAttachmentPart
export type Prompt = ContentPart[]
export const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }]
export function isPromptEqual(promptA: Prompt, promptB: Prompt): boolean {
if (promptA.length !== promptB.length) return false
for (let i = 0; i < promptA.length; i++) {
const partA = promptA[i]
const partB = promptB[i]
if (partA.type !== partB.type) return false
if (partA.type === "text" && partA.content !== (partB as TextPart).content) {
return false
}
if (partA.type === "file" && partA.path !== (partB as FileAttachmentPart).path) {
return false
}
}
return true
}
+45 -18
View File
@@ -9,7 +9,8 @@ import type {
File,
FileNode,
Project,
Command,
FileDiff,
Todo,
} from "@opencode-ai/sdk"
import { createStore, produce, reconcile } from "solid-js/store"
import { createMemo } from "solid-js"
@@ -28,6 +29,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
config: Config
path: Path
session: Session[]
session_diff: {
[sessionID: string]: FileDiff[]
}
todo: {
[sessionID: string]: Todo[]
}
limit: number
message: {
[sessionID: string]: Message[]
}
@@ -44,6 +52,9 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
agent: [],
provider: [],
session: [],
session_diff: {},
todo: {},
limit: 10,
message: {},
part: {},
node: [],
@@ -68,6 +79,12 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
)
break
}
case "session.diff":
setStore("session_diff", event.properties.sessionID, event.properties.diff)
break
case "todo.updated":
setStore("todo", event.properties.sessionID, event.properties.todos)
break
case "message.updated": {
const messages = store.message[event.properties.info.sessionID]
if (!messages) {
@@ -118,12 +135,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
path: () => sdk.client.path.get().then((x) => setStore("path", x.data!)),
agent: () => sdk.client.app.agents().then((x) => setStore("agent", x.data ?? [])),
session: () =>
sdk.client.session.list().then((x) =>
setStore(
"session",
(x.data ?? []).slice().sort((a, b) => a.id.localeCompare(b.id)),
),
),
sdk.client.session.list().then((x) => {
const sessions = (x.data ?? [])
.slice()
.sort((a, b) => a.id.localeCompare(b.id))
.slice(0, store.limit)
setStore("session", sessions)
}),
config: () => sdk.client.config.get().then((x) => setStore("config", x.data!)),
changes: () => sdk.client.file.status().then((x) => setStore("changes", x.data!)),
node: () => sdk.client.file.list({ query: { path: "/" } }).then((x) => setStore("node", x.data!)),
@@ -167,22 +185,19 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
if (match.found) return store.session[match.index]
return undefined
},
async sync(sessionID: string, isRetry = false) {
const [session, messages] = await Promise.all([
sdk.client.session.get({ path: { id: sessionID } }),
async sync(sessionID: string, _isRetry = false) {
const [session, messages, todo, diff] = await Promise.all([
sdk.client.session.get({ path: { id: sessionID }, throwOnError: true }),
sdk.client.session.messages({ path: { id: sessionID } }),
sdk.client.session.todo({ path: { id: sessionID } }),
sdk.client.session.diff({ path: { id: sessionID } }),
])
// If no messages and this might be a new session, retry after a delay
if (!isRetry && messages.data!.length === 0) {
setTimeout(() => this.sync(sessionID, true), 500)
return
}
setStore(
produce((draft) => {
const match = Binary.search(draft.session, sessionID, (s) => s.id)
draft.session[match.index] = session.data!
if (match.found) draft.session[match.index] = session.data!
if (!match.found) draft.session.splice(match.index, 0, session.data!)
draft.todo[sessionID] = todo.data ?? []
draft.message[sessionID] = messages
.data!.map((x) => x.info)
.slice()
@@ -193,9 +208,21 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
.map(sanitizePart)
.sort((a, b) => a.id.localeCompare(b.id))
}
draft.session_diff[sessionID] = diff.data ?? []
}),
)
// If no messages and this might be a new session, retry after a delay
// if (!isRetry && messages.data!.length === 0) {
// setTimeout(() => this.sync(sessionID, true), 500)
// return
// }
},
fetch: async (count = 10) => {
setStore("limit", (x) => x + count)
await load.session()
},
more: createMemo(() => store.session.length === store.limit),
},
load,
absolute,
+10 -4
View File
@@ -7,14 +7,18 @@ import { Fonts, ShikiProvider, MarkedProvider } from "@opencode-ai/ui"
import { SDKProvider } from "./context/sdk"
import { SyncProvider } from "./context/sync"
import { LocalProvider } from "./context/local"
import Home from "@/pages"
import Layout from "@/pages/layout"
import SessionLayout from "@/pages/session-layout"
import Session from "@/pages/session"
const host = import.meta.env.VITE_OPENCODE_SERVER_HOST ?? "127.0.0.1"
const port = import.meta.env.VITE_OPENCODE_SERVER_PORT ?? "4096"
const url =
new URLSearchParams(document.location.search).get("url") ||
(location.hostname.includes("opencode.ai") ? `http://${host}:${port}` : "/")
(location.hostname.includes("opencode.ai") || location.hostname.includes("localhost")
? `http://${host}:${port}`
: "/")
const root = document.getElementById("root")
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
@@ -32,8 +36,10 @@ render(
<LocalProvider>
<MetaProvider>
<Fonts />
<Router>
<Route path="/" component={Home} />
<Router root={Layout}>
<Route path={["/", "/session"]} component={SessionLayout}>
<Route path="/:id?" component={Session} />
</Route>
</Router>
</MetaProvider>
</LocalProvider>
-857
View File
@@ -1,857 +0,0 @@
import {
Button,
List,
SelectDialog,
Tooltip,
IconButton,
Tabs,
Icon,
Accordion,
Diff,
Collapsible,
DiffChanges,
Message,
Typewriter,
Card,
Code,
} from "@opencode-ai/ui"
import { FileIcon } from "@/ui"
import FileTree from "@/components/file-tree"
import { MessageProgress } from "@/components/message-progress"
import { For, onCleanup, onMount, Show, Match, Switch, createSignal, createEffect, createMemo } from "solid-js"
import { useLocal, type LocalFile } from "@/context/local"
import { createStore } from "solid-js/store"
import { getDirectory, getFilename } from "@/utils"
import { ContentPart, PromptInput } from "@/components/prompt-input"
import { DateTime } from "luxon"
import {
DragDropProvider,
DragDropSensors,
DragOverlay,
SortableProvider,
closestCenter,
createSortable,
useDragDropContext,
} from "@thisbeyond/solid-dnd"
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
import type { JSX } from "solid-js"
import { useSync } from "@/context/sync"
import { useSDK } from "@/context/sdk"
import { type AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk"
import { Markdown } from "@opencode-ai/ui"
import { Spinner } from "@/components/spinner"
export default function Page() {
const local = useLocal()
const sync = useSync()
const sdk = useSDK()
const [store, setStore] = createStore({
clickTimer: undefined as number | undefined,
fileSelectOpen: false,
})
let inputRef!: HTMLDivElement
let messageScrollElement!: HTMLDivElement
const [activeItem, setActiveItem] = createSignal<string | undefined>(undefined)
const MOD = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) ? "Meta" : "Control"
onMount(() => {
document.addEventListener("keydown", handleKeyDown)
})
onCleanup(() => {
document.removeEventListener("keydown", handleKeyDown)
})
const handleKeyDown = (event: KeyboardEvent) => {
if (event.getModifierState(MOD) && event.shiftKey && event.key.toLowerCase() === "p") {
event.preventDefault()
return
}
if (event.getModifierState(MOD) && event.key.toLowerCase() === "p") {
event.preventDefault()
setStore("fileSelectOpen", true)
return
}
const focused = document.activeElement === inputRef
if (focused) {
if (event.key === "Escape") {
inputRef?.blur()
}
return
}
// if (local.file.active()) {
// const active = local.file.active()!
// if (event.key === "Enter" && active.selection) {
// local.context.add({
// type: "file",
// path: active.path,
// selection: { ...active.selection },
// })
// return
// }
//
// if (event.getModifierState(MOD)) {
// if (event.key.toLowerCase() === "a") {
// return
// }
// if (event.key.toLowerCase() === "c") {
// return
// }
// }
// }
if (event.key.length === 1 && event.key !== "Unidentified" && !(event.ctrlKey || event.metaKey)) {
inputRef?.focus()
}
}
const resetClickTimer = () => {
if (!store.clickTimer) return
clearTimeout(store.clickTimer)
setStore("clickTimer", undefined)
}
const startClickTimer = () => {
const newClickTimer = setTimeout(() => {
setStore("clickTimer", undefined)
}, 300)
setStore("clickTimer", newClickTimer as unknown as number)
}
const handleFileClick = async (file: LocalFile) => {
if (store.clickTimer) {
resetClickTimer()
local.file.update(file.path, { ...file, pinned: true })
} else {
local.file.open(file.path)
startClickTimer()
}
}
// const navigateChange = (dir: 1 | -1) => {
// const active = local.file.active()
// if (!active) return
// const current = local.file.changeIndex(active.path)
// const next = current === undefined ? (dir === 1 ? 0 : -1) : current + dir
// local.file.setChangeIndex(active.path, next)
// }
const handleTabChange = (path: string) => {
local.session.setActiveTab(path)
if (path === "chat") return
local.session.open(path)
}
const handleTabClose = (file: LocalFile) => {
local.session.close(file.path)
}
const handleDragStart = (event: unknown) => {
const id = getDraggableId(event)
if (!id) return
setActiveItem(id)
}
const handleDragOver = (event: DragEvent) => {
const { draggable, droppable } = event
if (draggable && droppable) {
const currentFiles = local.session.tabs()?.opened.map((file) => file)
const fromIndex = currentFiles?.indexOf(draggable.id.toString())
const toIndex = currentFiles?.indexOf(droppable.id.toString())
if (fromIndex !== toIndex && toIndex !== undefined) {
local.session.move(draggable.id.toString(), toIndex)
}
}
}
const handleDragEnd = () => {
setActiveItem(undefined)
}
// const scrollDiffItem = (element: HTMLElement) => {
// element.scrollIntoView({ block: "start", behavior: "instant" })
// }
const handleDiffTriggerClick = (event: MouseEvent) => {
// disabling scroll to diff for now
return
// const target = event.currentTarget as HTMLElement
// queueMicrotask(() => {
// if (target.getAttribute("aria-expanded") !== "true") return
// const item = target.closest('[data-slot="accordion-item"]') as HTMLElement | null
// if (!item) return
// scrollDiffItem(item)
// })
}
const handlePromptSubmit = async (parts: ContentPart[]) => {
const existingSession = local.session.active()
let session = existingSession
if (!session) {
const created = await sdk.client.session.create()
session = created.data ?? undefined
}
if (!session) return
local.session.setActive(session.id)
if (!existingSession) {
local.session.copyTabs("", session.id)
}
local.session.setActiveTab(undefined)
const toAbsolutePath = (path: string) => (path.startsWith("/") ? path : sync.absolute(path))
const text = parts.map((part) => part.content).join("")
const attachments = parts.filter((part) => part.type === "file")
// const activeFile = local.context.active()
// if (activeFile) {
// registerAttachment(
// activeFile.path,
// activeFile.selection,
// activeFile.name ?? formatAttachmentLabel(activeFile.path, activeFile.selection),
// )
// }
// for (const contextFile of local.context.all()) {
// registerAttachment(
// contextFile.path,
// contextFile.selection,
// formatAttachmentLabel(contextFile.path, contextFile.selection),
// )
// }
const attachmentParts = attachments.map((attachment) => {
const absolute = toAbsolutePath(attachment.path)
const query = attachment.selection
? `?start=${attachment.selection.startLine}&end=${attachment.selection.endLine}`
: ""
return {
type: "file" as const,
mime: "text/plain",
url: `file://${absolute}${query}`,
filename: getFilename(attachment.path),
source: {
type: "file" as const,
text: {
value: attachment.content,
start: attachment.start,
end: attachment.end,
},
path: absolute,
},
}
})
await sdk.client.session.prompt({
path: { id: session.id },
body: {
agent: local.agent.current()!.name,
model: {
modelID: local.model.current()!.id,
providerID: local.model.current()!.provider.id,
},
parts: [
{
type: "text",
text,
},
...attachmentParts,
],
},
})
}
const handleNewSession = () => {
local.session.setActive(undefined)
inputRef?.focus()
}
const TabVisual = (props: { file: LocalFile }): JSX.Element => {
return (
<div class="flex items-center gap-x-1.5">
<FileIcon node={props.file} class="grayscale-100 group-data-[selected]/tab:grayscale-0" />
<span
classList={{
"text-14-medium": true,
"text-primary": !!props.file.status?.status,
italic: !props.file.pinned,
}}
>
{props.file.name}
</span>
<span class="hidden opacity-70">
<Switch>
<Match when={props.file.status?.status === "modified"}>
<span class="text-primary">M</span>
</Match>
<Match when={props.file.status?.status === "added"}>
<span class="text-success">A</span>
</Match>
<Match when={props.file.status?.status === "deleted"}>
<span class="text-error">D</span>
</Match>
</Switch>
</span>
</div>
)
}
const SortableTab = (props: {
file: LocalFile
onTabClick: (file: LocalFile) => void
onTabClose: (file: LocalFile) => void
}): JSX.Element => {
const sortable = createSortable(props.file.path)
return (
// @ts-ignore
<div use:sortable classList={{ "h-full": true, "opacity-0": sortable.isActiveDraggable }}>
<Tooltip value={props.file.path} placement="bottom" class="h-full">
<div class="relative h-full">
<Tabs.Trigger
value={props.file.path}
class="group/tab pl-3 pr-1"
onClick={() => props.onTabClick(props.file)}
>
<TabVisual file={props.file} />
<IconButton
icon="close"
class="mt-0.5 opacity-0 text-text-muted/60 group-data-[selected]/tab:opacity-100
group-data-[selected]/tab:text-text group-data-[selected]/tab:hover:bg-border-subtle
hover:opacity-100 group-hover/tab:opacity-100"
variant="ghost"
onClick={() => props.onTabClose(props.file)}
/>
</Tabs.Trigger>
</div>
</Tooltip>
</div>
)
}
const ConstrainDragYAxis = (): JSX.Element => {
const context = useDragDropContext()
if (!context) return <></>
const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context
const transformer: Transformer = {
id: "constrain-y-axis",
order: 100,
callback: (transform) => ({ ...transform, y: 0 }),
}
onDragStart((event) => {
const id = getDraggableId(event)
if (!id) return
addTransformer("draggables", id, transformer)
})
onDragEnd((event) => {
const id = getDraggableId(event)
if (!id) return
removeTransformer("draggables", id, transformer.id)
})
return <></>
}
const getDraggableId = (event: unknown): string | undefined => {
if (typeof event !== "object" || event === null) return undefined
if (!("draggable" in event)) return undefined
const draggable = (event as { draggable?: { id?: unknown } }).draggable
if (!draggable) return undefined
return typeof draggable.id === "string" ? draggable.id : undefined
}
return (
<div class="relative h-screen flex flex-col">
<header class="hidden h-12 shrink-0 bg-background-strong border-b border-border-weak-base"></header>
<main class="h-[calc(100vh-0rem)] flex">
<div class="w-70 shrink-0 bg-background-weak border-r border-border-weak-base flex flex-col items-start">
<div class="h-10 flex items-center self-stretch px-5 border-b border-border-weak-base">
<span class="text-14-regular overflow-hidden text-ellipsis">{getFilename(sync.data.path.directory)}</span>
</div>
<div class="flex flex-col items-start gap-4 self-stretch flex-1 py-4 px-3">
<Button class="w-full" size="large" onClick={handleNewSession} icon="edit-small-2">
New Session
</Button>
<List
data={sync.data.session}
key={(x) => x.id}
current={local.session.active()}
onSelect={(s) => local.session.setActive(s?.id)}
onHover={(s) => (!!s ? sync.session.sync(s?.id) : undefined)}
>
{(session) => {
const diffs = createMemo(() => session.summary?.diffs ?? [])
const filesChanged = createMemo(() => diffs().length)
const updated = DateTime.fromMillis(session.time.updated)
return (
<Tooltip placement="right" value={session.title}>
<div>
<div class="flex items-center self-stretch gap-6 justify-between">
<span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
{session.title}
</span>
<span class="text-12-regular text-text-weak text-right whitespace-nowrap">
{Math.abs(updated.diffNow().as("seconds")) < 60
? "Now"
: updated
.toRelative({ style: "short", unit: ["days", "hours", "minutes"] })
?.replace(" ago", "")
?.replace(/ days?/, "d")
?.replace(" min.", "m")
?.replace(" hr.", "h")}
</span>
</div>
<div class="flex justify-between items-center self-stretch">
<span class="text-12-regular text-text-weak">{`${filesChanged() || "No"} file${filesChanged() !== 1 ? "s" : ""} changed`}</span>
<DiffChanges diff={diffs()} />
</div>
</div>
</Tooltip>
)
}}
</List>
</div>
</div>
<div class="relative bg-background-base w-full h-full overflow-x-hidden">
<DragDropProvider
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
collisionDetector={closestCenter}
>
<DragDropSensors />
<ConstrainDragYAxis />
<Tabs value={local.session.tabs()?.active ?? "chat"} onChange={handleTabChange}>
<div class="sticky top-0 shrink-0 flex">
<Tabs.List>
<Tabs.Trigger value="chat" class="flex gap-x-4 items-center">
<div>Chat</div>
{/* <Tooltip value={`${local.session.tokens() ?? 0} Tokens`} class="flex items-center gap-1.5"> */}
{/* <ProgressCircle percentage={local.session.context() ?? 0} /> */}
{/* <div class="text-14-regular text-text-weak text-right">{local.session.context() ?? 0}%</div> */}
{/* </Tooltip> */}
</Tabs.Trigger>
{/* <Tabs.Trigger value="review">Review</Tabs.Trigger> */}
<SortableProvider ids={local.session.tabs()?.opened ?? []}>
<For each={local.session.tabs()?.opened ?? []}>
{(file) => (
<SortableTab
file={local.file.node(file)}
onTabClick={handleFileClick}
onTabClose={handleTabClose}
/>
)}
</For>
</SortableProvider>
<div class="bg-background-base h-full flex items-center justify-center border-b border-border-weak-base px-3">
<IconButton
icon="plus-small"
variant="ghost"
iconSize="large"
onClick={() => setStore("fileSelectOpen", true)}
/>
</div>
</Tabs.List>
</div>
<Tabs.Content value="chat" class="@container select-text flex flex-col flex-1 min-h-0 overflow-y-hidden">
<div class="relative px-6 pt-12 max-w-2xl w-full mx-auto flex flex-col flex-1 min-h-0">
<Show
when={local.session.active()}
fallback={
<div class="flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch">
<div class="text-20-medium text-text-weaker">New session</div>
<div class="flex justify-center items-center gap-3">
<Icon name="folder" size="small" />
<div class="text-12-medium text-text-weak">
{getDirectory(sync.data.path.directory)}
<span class="text-text-strong">{getFilename(sync.data.path.directory)}</span>
</div>
</div>
<div class="flex justify-center items-center gap-3">
<Icon name="pencil-line" size="small" />
<div class="text-12-medium text-text-weak">
Last modified&nbsp;
<span class="text-text-strong">
{DateTime.fromMillis(sync.data.project.time.created).toRelative()}
</span>
</div>
</div>
</div>
}
>
{(session) => {
const [store, setStore] = createStore<{
messageId?: string
}>()
const messages = createMemo(() => sync.data.message[session().id] ?? [])
const userMessages = createMemo(() =>
messages()
.filter((m) => m.role === "user")
.sort((a, b) => b.id.localeCompare(a.id)),
)
const lastUserMessage = createMemo(() => {
return userMessages()?.at(0)
})
const activeMessage = createMemo(() => {
if (!store.messageId) return lastUserMessage()
return userMessages()?.find((m) => m.id === store.messageId)
})
return (
<div class="pt-3 flex flex-col flex-1 min-h-0">
<div class="flex-1 min-h-0">
<Show when={userMessages().length > 1}>
<ul
role="list"
class="absolute right-full mr-8 hidden w-60 shrink-0 @7xl:flex flex-col items-start gap-1"
>
<For each={userMessages()}>
{(message) => {
const diffs = createMemo(() => message.summary?.diffs ?? [])
const assistantMessages = createMemo(() => {
return sync.data.message[session().id]?.filter(
(m) => m.role === "assistant" && m.parentID == message.id,
) as AssistantMessageType[]
})
const error = createMemo(() => assistantMessages().find((m) => m?.error)?.error)
const working = createMemo(() => !message.summary?.body && !error())
return (
<li class="group/li flex items-center self-stretch">
<button
class="flex items-center self-stretch w-full gap-x-2 py-1 cursor-default"
onClick={() => setStore("messageId", message.id)}
>
<Switch>
<Match when={working()}>
<Spinner class="text-text-base shrink-0 w-[18px] aspect-square" />
</Match>
<Match when={true}>
<DiffChanges diff={diffs()} variant="bars" />
</Match>
</Switch>
<div
data-active={activeMessage()?.id === message.id}
classList={{
"text-14-regular text-text-weak whitespace-nowrap truncate min-w-0": true,
"text-text-weak data-[active=true]:text-text-strong group-hover/li:text-text-base": true,
}}
>
<Show when={message.summary?.title} fallback="New message">
{message.summary?.title}
</Show>
</div>
</button>
</li>
)
}}
</For>
</ul>
</Show>
<div ref={messageScrollElement} class="grow min-w-0 h-full overflow-y-auto no-scrollbar">
<For each={userMessages()}>
{(message) => {
const isActive = createMemo(() => activeMessage()?.id === message.id)
const [titled, setTitled] = createSignal(!!message.summary?.title)
const assistantMessages = createMemo(() => {
return sync.data.message[session().id]?.filter(
(m) => m.role === "assistant" && m.parentID == message.id,
) as AssistantMessageType[]
})
const error = createMemo(() => assistantMessages().find((m) => m?.error)?.error)
const [completed, setCompleted] = createSignal(!!message.summary?.body || !!error())
const [expanded, setExpanded] = createSignal(false)
const parts = createMemo(() => sync.data.part[message.id])
const title = createMemo(() => message.summary?.title)
const summary = createMemo(() => message.summary?.body)
const diffs = createMemo(() => message.summary?.diffs ?? [])
const hasToolPart = createMemo(() =>
assistantMessages()
?.flatMap((m) => sync.data.part[m.id])
.some((p) => p?.type === "tool"),
)
const working = createMemo(() => !summary() && !error())
// allowing time for the animations to finish
createEffect(() => {
title()
setTimeout(() => setTitled(!!title()), 10_000)
})
createEffect(() => {
const complete = !!summary() || !!error()
setTimeout(() => setCompleted(complete), 1200)
})
return (
<Show when={isActive()}>
<div
data-message={message.id}
class="flex flex-col items-start self-stretch gap-8 pb-50"
>
{/* Title */}
<div class="py-2 flex flex-col items-start gap-2 self-stretch sticky top-0 bg-background-stronger z-10">
<div class="w-full text-14-medium text-text-strong">
<Show
when={titled()}
fallback={
<Typewriter
as="h1"
text={title()}
class="overflow-hidden text-ellipsis min-w-0 text-nowrap"
/>
}
>
<h1 class="overflow-hidden text-ellipsis min-w-0 text-nowrap">
{title()}
</h1>
</Show>
</div>
</div>
<div class="-mt-8">
<Message message={message} parts={parts()} />
</div>
{/* Summary */}
<Show when={completed()}>
<div class="w-full flex flex-col gap-6 items-start self-stretch">
<div class="flex flex-col items-start gap-1 self-stretch">
<h2 class="text-12-medium text-text-weak">
<Switch>
<Match when={diffs().length}>Summary</Match>
<Match when={true}>Response</Match>
</Switch>
</h2>
<Show when={summary()}>
{(summary) => (
<Markdown
classList={{ "[&>*]:fade-up-text": !diffs().length }}
text={summary()}
/>
)}
</Show>
</div>
<Accordion class="w-full" multiple>
<For each={diffs()}>
{(diff) => (
<Accordion.Item value={diff.file}>
<Accordion.Header>
<Accordion.Trigger onClick={handleDiffTriggerClick}>
<div class="flex items-center justify-between w-full gap-5">
<div class="grow flex items-center gap-5 min-w-0">
<FileIcon
node={{ path: diff.file, type: "file" }}
class="shrink-0 size-4"
/>
<div class="flex grow min-w-0">
<Show when={diff.file.includes("/")}>
<span class="text-text-base truncate-start">
{getDirectory(diff.file)}&lrm;
</span>
</Show>
<span class="text-text-strong shrink-0">
{getFilename(diff.file)}
</span>
</div>
</div>
<div class="shrink-0 flex gap-4 items-center justify-end">
<DiffChanges diff={diff} />
<Icon name="chevron-grabber-vertical" size="small" />
</div>
</div>
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content>
<Diff
before={{
name: diff.file!,
contents: diff.before!,
}}
after={{
name: diff.file!,
contents: diff.after!,
}}
/>
</Accordion.Content>
</Accordion.Item>
)}
</For>
</Accordion>
</div>
</Show>
<Show when={error() && !expanded()}>
<Card variant="error" class="text-text-on-critical-base">
{error()?.data?.message as string}
</Card>
</Show>
{/* Response */}
<div class="w-full">
<Switch>
<Match when={!completed()}>
<MessageProgress
assistantMessages={assistantMessages}
done={!working()}
/>
</Match>
<Match when={completed() && hasToolPart()}>
<Collapsible variant="ghost" open={expanded()} onOpenChange={setExpanded}>
<Collapsible.Trigger class="text-text-weak hover:text-text-strong">
<div class="flex items-center gap-1 self-stretch">
<div class="text-12-medium">
<Switch>
<Match when={expanded()}>Hide details</Match>
<Match when={!expanded()}>Show details</Match>
</Switch>
</div>
<Collapsible.Arrow />
</div>
</Collapsible.Trigger>
<Collapsible.Content>
<div class="w-full flex flex-col items-start self-stretch gap-3">
<For each={assistantMessages()}>
{(assistantMessage) => {
const parts = createMemo(
() => sync.data.part[assistantMessage.id],
)
return <Message message={assistantMessage} parts={parts()} />
}}
</For>
<Show when={error()}>
<Card variant="error" class="text-text-on-critical-base">
{error()?.data?.message as string}
</Card>
</Show>
</div>
</Collapsible.Content>
</Collapsible>
</Match>
</Switch>
</div>
</div>
</Show>
)
}}
</For>
</div>
</div>
</div>
)
}}
</Show>
</div>
</Tabs.Content>
{/* <Tabs.Content value="review" class="select-text"></Tabs.Content> */}
<For each={local.session.tabs()?.opened}>
{(file) => (
<Tabs.Content value={file} class="select-text">
{(() => {
{
/* const view = local.file.view(file) */
}
{
/* const showRaw = view === "raw" || !file.content?.diff */
}
{
/* const code = showRaw ? (file.content?.content ?? "") : (file.content?.diff ?? "") */
}
const node = local.file.node(file)
return (
<Code
file={{ name: node.path, contents: node.content?.content ?? "" }}
disableFileHeader
overflow="scroll"
class="pt-3 pb-40"
/>
)
})()}
</Tabs.Content>
)}
</For>
</Tabs>
<DragOverlay>
{(() => {
const id = activeItem()
if (!id) return null
const draggedFile = local.file.node(id)
if (!draggedFile) return null
return (
<div class="relative px-3 h-10 flex items-center bg-background-base border-x border-border-weak-base border-b border-b-transparent">
<TabVisual file={draggedFile} />
</div>
)
})()}
</DragOverlay>
</DragDropProvider>
<div class="absolute inset-x-0 px-6 max-w-2xl flex flex-col justify-center items-center z-50 mx-auto bottom-8">
<PromptInput
ref={(el) => {
inputRef = el
}}
onSubmit={handlePromptSubmit}
/>
</div>
<div class="hidden shrink-0 w-56 p-2 h-full overflow-y-auto">
<FileTree path="" onFileClick={handleFileClick} />
</div>
<div class="hidden shrink-0 w-56 p-2">
<Show
when={local.file.changes().length}
fallback={<div class="px-2 text-xs text-text-muted">No changes</div>}
>
<ul class="">
<For each={local.file.changes()}>
{(path) => (
<li>
<button
onClick={() => local.file.open(path, { view: "diff-unified", pinned: true })}
class="w-full flex items-center px-2 py-0.5 gap-x-2 text-text-muted grow min-w-0 hover:bg-background-element"
>
<FileIcon node={{ path, type: "file" }} class="shrink-0 size-3" />
<span class="text-xs text-text whitespace-nowrap">{getFilename(path)}</span>
<span class="text-xs text-text-muted/60 whitespace-nowrap truncate min-w-0">
{getDirectory(path)}
</span>
</button>
</li>
)}
</For>
</ul>
</Show>
</div>
</div>
</main>
<Show when={store.fileSelectOpen}>
<SelectDialog
defaultOpen
title="Select file"
placeholder="Search files"
emptyMessage="No files found"
items={local.file.searchFiles}
key={(x) => x}
onOpenChange={(open) => setStore("fileSelectOpen", open)}
onSelect={(x) => (x ? local.session.open(x) : undefined)}
>
{(i) => (
<div
classList={{
"w-full flex items-center justify-between rounded-md": true,
}}
>
<div class="flex items-center gap-x-2 grow min-w-0">
<FileIcon node={{ path: i, type: "file" }} class="shrink-0 size-4" />
<div class="flex items-center text-14-regular">
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
{getDirectory(i)}
</span>
<span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
</div>
</div>
<div class="flex items-center gap-x-1 text-text-muted/40 shrink-0"></div>
</div>
)}
</SelectDialog>
</Show>
</div>
)
}
+121
View File
@@ -0,0 +1,121 @@
import { Button, Tooltip, DiffChanges, IconButton } from "@opencode-ai/ui"
import { createMemo, For, ParentProps, Show } from "solid-js"
import { DateTime } from "luxon"
import { useSync } from "@/context/sync"
import { A, useParams } from "@solidjs/router"
import { useLocal } from "@/context/local"
export default function Layout(props: ParentProps) {
const params = useParams()
const sync = useSync()
const local = useLocal()
return (
<div class="relative h-screen flex flex-col">
<header class="hidden h-12 shrink-0 bg-background-strong border-b border-border-weak-base"></header>
<div class="h-[calc(100vh-0rem)] flex">
<div
classList={{
"@container w-14 pb-4 shrink-0 bg-background-weak": true,
"flex flex-col items-start self-stretch justify-between": true,
"border-r border-border-weak-base": true,
"w-70": local.layout.sidebar.opened(),
}}
>
<div class="flex flex-col justify-center items-start gap-4 self-stretch py-2 overflow-hidden mx-auto @[4rem]:mx-0">
<div class="h-8 shrink-0 flex items-center self-stretch px-3">
<Tooltip placement="right" value="Collapse sidebar">
<IconButton icon="layout-left" variant="ghost" size="large" onClick={local.layout.sidebar.toggle} />
</Tooltip>
</div>
<div class="w-full px-3">
<Button as={A} href="/session" class="hidden @[4rem]:flex w-full" size="large" icon="edit-small-2">
New Session
</Button>
<Tooltip placement="right" value="New session">
<IconButton as={A} href="/session" icon="edit-small-2" size="large" class="@[4rem]:hidden" />
</Tooltip>
</div>
<div class="hidden @[4rem]:flex size-full overflow-y-auto no-scrollbar flex-col flex-1 px-3">
<nav class="w-full">
<For each={sync.data.session}>
{(session) => {
const updated = createMemo(() => DateTime.fromMillis(session.time.updated))
return (
<A
data-active={session.id === params.id}
href={`/session/${session.id}`}
class="group/session focus:outline-none cursor-default"
>
<Tooltip placement="right" value={session.title}>
<div
class="w-full mb-1.5 px-3 py-1 rounded-md
group-data-[active=true]/session:bg-surface-raised-base-hover
group-hover/session:bg-surface-raised-base-hover
group-focus/session:bg-surface-raised-base-hover"
>
<div class="flex items-center self-stretch gap-6 justify-between">
<span class="text-14-regular text-text-strong overflow-hidden text-ellipsis truncate">
{session.title}
</span>
<span class="text-12-regular text-text-weak text-right whitespace-nowrap">
{Math.abs(updated().diffNow().as("seconds")) < 60
? "Now"
: updated()
.toRelative({ style: "short", unit: ["days", "hours", "minutes"] })
?.replace(" ago", "")
?.replace(/ days?/, "d")
?.replace(" min.", "m")
?.replace(" hr.", "h")}
</span>
</div>
<div class="flex justify-between items-center self-stretch">
<span class="text-12-regular text-text-weak">{`${session.summary?.files || "No"} file${session.summary?.files !== 1 ? "s" : ""} changed`}</span>
<Show when={session.summary}>{(summary) => <DiffChanges changes={summary()} />}</Show>
</div>
</div>
</Tooltip>
</A>
)
}}
</For>
</nav>
<Show when={sync.session.more()}>
<button
class="shrink-0 self-start p-3 text-12-medium text-text-weak hover:text-text-strong"
onClick={() => sync.session.fetch()}
>
Show more
</button>
</Show>
</div>
</div>
<div class="flex flex-col items-start shrink-0 px-3 py-1 mx-auto @[4rem]:mx-0">
<Button
as={"a"}
href="https://opencode.ai/desktop-feedback"
target="_blank"
class="hidden @[4rem]:flex w-full text-12-medium text-text-base stroke-[1.5px]"
variant="ghost"
icon="speech-bubble"
>
Share feedback
</Button>
<Tooltip placement="right" value="Share feedback">
<IconButton
as={"a"}
href="https://opencode.ai/desktop-feedback"
target="_blank"
icon="speech-bubble"
variant="ghost"
size="large"
class="@[4rem]:hidden stroke-[1.5px]"
/>
</Tooltip>
</div>
</div>
<main class="size-full overflow-x-hidden">{props.children}</main>
</div>
</div>
)
}
@@ -0,0 +1,12 @@
import { Show, type ParentProps } from "solid-js"
import { SessionProvider } from "@/context/session"
import { useParams } from "@solidjs/router"
export default function Layout(props: ParentProps) {
const params = useParams()
return (
<Show when={params.id || true} keyed>
<SessionProvider sessionId={params.id}>{props.children}</SessionProvider>
</Show>
)
}
+894
View File
@@ -0,0 +1,894 @@
import {
SelectDialog,
IconButton,
Tabs,
Icon,
Accordion,
Diff,
Collapsible,
DiffChanges,
Message,
Typewriter,
Card,
Code,
Tooltip,
ProgressCircle,
Button,
} from "@opencode-ai/ui"
import { FileIcon } from "@/ui"
import { MessageProgress } from "@/components/message-progress"
import {
For,
onCleanup,
onMount,
Show,
Match,
Switch,
createSignal,
createEffect,
createMemo,
createResource,
} from "solid-js"
import { useLocal, type LocalFile } from "@/context/local"
import { createStore } from "solid-js/store"
import { getDirectory, getFilename } from "@/utils"
import { PromptInput } from "@/components/prompt-input"
import { DateTime } from "luxon"
import {
DragDropProvider,
DragDropSensors,
DragOverlay,
SortableProvider,
closestCenter,
createSortable,
useDragDropContext,
} from "@thisbeyond/solid-dnd"
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
import type { JSX } from "solid-js"
import { useSync } from "@/context/sync"
import { type AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk"
import { Markdown } from "@opencode-ai/ui"
import { Spinner } from "@/components/spinner"
import { useSession } from "@/context/session"
export default function Page() {
const local = useLocal()
const sync = useSync()
const session = useSession()
const [store, setStore] = createStore({
clickTimer: undefined as number | undefined,
fileSelectOpen: false,
activeDraggable: undefined as string | undefined,
})
let inputRef!: HTMLDivElement
let messageScrollElement!: HTMLDivElement
const MOD = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) ? "Meta" : "Control"
onMount(() => {
document.addEventListener("keydown", handleKeyDown)
})
onCleanup(() => {
document.removeEventListener("keydown", handleKeyDown)
})
const handleKeyDown = (event: KeyboardEvent) => {
if (event.getModifierState(MOD) && event.shiftKey && event.key.toLowerCase() === "p") {
event.preventDefault()
return
}
if (event.getModifierState(MOD) && event.key.toLowerCase() === "p") {
event.preventDefault()
setStore("fileSelectOpen", true)
return
}
const focused = document.activeElement === inputRef
if (focused) {
if (event.key === "Escape") {
inputRef?.blur()
}
return
}
// if (local.file.active()) {
// const active = local.file.active()!
// if (event.key === "Enter" && active.selection) {
// local.context.add({
// type: "file",
// path: active.path,
// selection: { ...active.selection },
// })
// return
// }
//
// if (event.getModifierState(MOD)) {
// if (event.key.toLowerCase() === "a") {
// return
// }
// if (event.key.toLowerCase() === "c") {
// return
// }
// }
// }
if (event.key.length === 1 && event.key !== "Unidentified" && !(event.ctrlKey || event.metaKey)) {
inputRef?.focus()
}
}
const resetClickTimer = () => {
if (!store.clickTimer) return
clearTimeout(store.clickTimer)
setStore("clickTimer", undefined)
}
const startClickTimer = () => {
const newClickTimer = setTimeout(() => {
setStore("clickTimer", undefined)
}, 300)
setStore("clickTimer", newClickTimer as unknown as number)
}
const handleTabClick = async (tab: string) => {
if (store.clickTimer) {
resetClickTimer()
// local.file.update(file.path, { ...file, pinned: true })
} else {
if (tab.startsWith("file://")) {
local.file.open(tab.replace("file://", ""))
}
startClickTimer()
}
}
const handleDragStart = (event: unknown) => {
const id = getDraggableId(event)
if (!id) return
setStore("activeDraggable", id)
}
const handleDragOver = (event: DragEvent) => {
const { draggable, droppable } = event
if (draggable && droppable) {
const currentTabs = session.layout.tabs.opened
const fromIndex = currentTabs?.indexOf(draggable.id.toString())
const toIndex = currentTabs?.indexOf(droppable.id.toString())
if (fromIndex !== toIndex && toIndex !== undefined) {
session.layout.moveTab(draggable.id.toString(), toIndex)
}
}
}
const handleDragEnd = () => {
setStore("activeDraggable", undefined)
}
const FileVisual = (props: { file: LocalFile }): JSX.Element => {
return (
<div class="flex items-center gap-x-1.5">
<FileIcon node={props.file} class="grayscale-100 group-data-[selected]/tab:grayscale-0" />
<span
classList={{
"text-14-medium": true,
"text-primary": !!props.file.status?.status,
italic: !props.file.pinned,
}}
>
{props.file.name}
</span>
<span class="hidden opacity-70">
<Switch>
<Match when={props.file.status?.status === "modified"}>
<span class="text-primary">M</span>
</Match>
<Match when={props.file.status?.status === "added"}>
<span class="text-success">A</span>
</Match>
<Match when={props.file.status?.status === "deleted"}>
<span class="text-error">D</span>
</Match>
</Switch>
</span>
</div>
)
}
const SortableTab = (props: {
tab: string
onTabClick: (tab: string) => void
onTabClose: (tab: string) => void
}): JSX.Element => {
const sortable = createSortable(props.tab)
const [file] = createResource(
() => props.tab,
async (tab) => {
if (tab.startsWith("file://")) {
return local.file.node(tab.replace("file://", ""))
}
return undefined
},
)
return (
// @ts-ignore
<div use:sortable classList={{ "h-full": true, "opacity-0": sortable.isActiveDraggable }}>
<div class="relative h-full">
<Tabs.Trigger value={props.tab} class="group/tab pl-3 pr-1" onClick={() => props.onTabClick(props.tab)}>
<Switch>
<Match when={file()}>{(f) => <FileVisual file={f()} />}</Match>
</Switch>
<IconButton
icon="close"
class="mt-0.5 opacity-0 group-data-[selected]/tab:opacity-100
hover:bg-transparent
hover:opacity-100 group-hover/tab:opacity-100"
variant="ghost"
onClick={() => props.onTabClose(props.tab)}
/>
</Tabs.Trigger>
</div>
</div>
)
}
const ConstrainDragYAxis = (): JSX.Element => {
const context = useDragDropContext()
if (!context) return <></>
const [, { onDragStart, onDragEnd, addTransformer, removeTransformer }] = context
const transformer: Transformer = {
id: "constrain-y-axis",
order: 100,
callback: (transform) => ({ ...transform, y: 0 }),
}
onDragStart((event) => {
const id = getDraggableId(event)
if (!id) return
addTransformer("draggables", id, transformer)
})
onDragEnd((event) => {
const id = getDraggableId(event)
if (!id) return
removeTransformer("draggables", id, transformer.id)
})
return <></>
}
const getDraggableId = (event: unknown): string | undefined => {
if (typeof event !== "object" || event === null) return undefined
if (!("draggable" in event)) return undefined
const draggable = (event as { draggable?: { id?: unknown } }).draggable
if (!draggable) return undefined
return typeof draggable.id === "string" ? draggable.id : undefined
}
return (
<div class="relative bg-background-base size-full overflow-x-hidden">
<DragDropProvider
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragOver={handleDragOver}
collisionDetector={closestCenter}
>
<DragDropSensors />
<ConstrainDragYAxis />
<Tabs value={session.layout.tabs.active ?? "chat"} onChange={session.layout.openTab}>
<div class="sticky top-0 shrink-0 flex">
<Tabs.List>
<Tabs.Trigger value="chat" class="flex gap-x-4 items-center">
<div>Chat</div>
<Tooltip
value={`${new Intl.NumberFormat("en-US", {
notation: "compact",
compactDisplay: "short",
}).format(session.usage.tokens() ?? 0)} Tokens`}
class="flex items-center gap-1.5"
>
<ProgressCircle percentage={session.usage.context() ?? 0} />
<div class="text-14-regular text-text-weak text-left w-7">{session.usage.context() ?? 0}%</div>
</Tooltip>
</Tabs.Trigger>
<Show when={local.layout.review.state() === "tab" && session.diffs().length}>
<Tabs.Trigger value="review" class="flex gap-3 items-center group/tab pr-1">
<Show when={session.diffs()}>
<DiffChanges changes={session.diffs()} variant="bars" />
</Show>
<div class="flex items-center gap-1.5">
<div>Review</div>
<Show when={session.info()?.summary?.files}>
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
{session.info()?.summary?.files ?? 0}
</div>
</Show>
<IconButton
icon="close"
class="mt-0.5 -ml-1 opacity-0 group-data-[selected]/tab:opacity-100
hover:bg-transparent hover:opacity-100 group-hover/tab:opacity-100"
variant="ghost"
onClick={local.layout.review.close}
/>
</div>
</Tabs.Trigger>
</Show>
<SortableProvider ids={session.layout.tabs.opened ?? []}>
<For each={session.layout.tabs.opened ?? []}>
{(tab) => <SortableTab tab={tab} onTabClick={handleTabClick} onTabClose={session.layout.closeTab} />}
</For>
</SortableProvider>
<div class="bg-background-base h-full flex items-center justify-center border-b border-border-weak-base px-3">
<Tooltip value="Open file" class="flex items-center">
<IconButton
icon="plus-small"
variant="ghost"
iconSize="large"
onClick={() => setStore("fileSelectOpen", true)}
/>
</Tooltip>
</div>
</Tabs.List>
</div>
<Tabs.Content value="chat" class="@container select-text flex flex-col flex-1 min-h-0 overflow-y-hidden">
<Show
when={session.id}
fallback={
<div class="size-full max-w-2xl mx-auto flex flex-col pb-45 px-6 justify-end items-start gap-4 flex-[1_0_0] self-stretch">
<div class="text-20-medium text-text-weaker">New session</div>
<div class="flex justify-center items-center gap-3">
<Icon name="folder" size="small" />
<div class="text-12-medium text-text-weak">
{getDirectory(sync.data.path.directory)}
<span class="text-text-strong">{getFilename(sync.data.path.directory)}</span>
</div>
</div>
<div class="flex justify-center items-center gap-3">
<Icon name="pencil-line" size="small" />
<div class="text-12-medium text-text-weak">
Last modified&nbsp;
<span class="text-text-strong">
{DateTime.fromMillis(sync.data.project.time.created).toRelative()}
</span>
</div>
</div>
</div>
}
>
{(_) => {
return (
<div
classList={{
"w-full grid flex-1 _gap-6 min-h-0": true,
"grid-cols-2": local.layout.review.state() === "open",
"max-w-2xl mx-auto": local.layout.review.state() !== "open",
}}
>
<div class="relative px-6 py-2 w-full flex flex-col gap-6 flex-1 min-h-0">
<div class="h-8 flex shrink-0 self-stretch items-center justify-end">
<Show when={local.layout.review.state() === "closed" && session.diffs().length}>
<Button icon="layout-right" onClick={local.layout.review.open}>
Review
</Button>
</Show>
</div>
<div
classList={{
"flex-1 min-h-0": true,
"flex items-start justify-start": local.layout.review.state() === "open",
}}
>
<Show when={session.messages.user().length > 1}>
<ul
role="list"
classList={{
"mr-8 shrink-0 flex flex-col items-start": true,
"absolute right-full w-60 @7xl:gap-2": local.layout.review.state() !== "open",
"": local.layout.review.state() === "open",
}}
>
<For each={session.messages.user()}>
{(message) => {
const assistantMessages = createMemo(() => {
if (!session.id) return []
return sync.data.message[session.id]?.filter(
(m) => m.role === "assistant" && m.parentID == message.id,
) as AssistantMessageType[]
})
const error = createMemo(() => assistantMessages().find((m) => m?.error)?.error)
const working = createMemo(() => !message.summary?.body && !error())
const handleClick = () => session.messages.setActive(message.id)
return (
<li
classList={{
"group/li flex items-center self-stretch justify-end": true,
"@7xl:justify-start": local.layout.review.state() !== "open",
}}
>
<Tooltip
placement="right"
gutter={8}
value={
<div class="flex items-center gap-2">
<DiffChanges changes={message.summary?.diffs ?? []} variant="bars" />
{message.summary?.title}
</div>
}
>
<button
data-active={session.messages.active()?.id === message.id}
onClick={handleClick}
classList={{
"group/tick flex items-center justify-start h-2 w-8 -mr-3": true,
"data-[active=true]:[&>div]:bg-icon-strong-base data-[active=true]:[&>div]:w-full": true,
"@7xl:hidden": local.layout.review.state() !== "open",
}}
>
<div class="h-px w-5 bg-icon-base group-hover/tick:w-full group-hover/tick:bg-icon-strong-base" />
</button>
</Tooltip>
<button
classList={{
"hidden items-center self-stretch w-full gap-x-2 cursor-default": true,
"@7xl:flex": local.layout.review.state() !== "open",
}}
onClick={handleClick}
>
<Switch>
<Match when={working()}>
<Spinner class="text-text-base shrink-0 w-[18px] aspect-square" />
</Match>
<Match when={true}>
<DiffChanges changes={message.summary?.diffs ?? []} variant="bars" />
</Match>
</Switch>
<div
data-active={session.messages.active()?.id === message.id}
classList={{
"text-14-regular text-text-weak whitespace-nowrap truncate min-w-0": true,
"text-text-weak data-[active=true]:text-text-strong group-hover/li:text-text-base": true,
}}
>
<Show when={message.summary?.title} fallback="New message">
{message.summary?.title}
</Show>
</div>
</button>
</li>
)
}}
</For>
</ul>
</Show>
<div ref={messageScrollElement} class="grow w-full min-w-0 h-full overflow-y-auto no-scrollbar">
<For each={session.messages.user()}>
{(message) => {
const isActive = createMemo(() => session.messages.active()?.id === message.id)
const [titled, setTitled] = createSignal(!!message.summary?.title)
const assistantMessages = createMemo(() => {
if (!session.id) return []
return sync.data.message[session.id]?.filter(
(m) => m.role === "assistant" && m.parentID == message.id,
) as AssistantMessageType[]
})
const error = createMemo(() => assistantMessages().find((m) => m?.error)?.error)
const [completed, setCompleted] = createSignal(!!message.summary?.body || !!error())
const [detailsExpanded, setDetailsExpanded] = createSignal(false)
const parts = createMemo(() => sync.data.part[message.id])
const hasToolPart = createMemo(() =>
assistantMessages()
?.flatMap((m) => sync.data.part[m.id])
.some((p) => p?.type === "tool"),
)
const working = createMemo(() => !message.summary?.body && !error())
// allowing time for the animations to finish
createEffect(() => {
const title = message.summary?.title
setTimeout(() => setTitled(!!title), 10_000)
})
createEffect(() => {
const summary = message.summary?.body
const complete = !!summary || !!error()
setTimeout(() => setCompleted(complete), 1200)
})
return (
<Show when={isActive()}>
<div
data-message={message.id}
class="flex flex-col items-start self-stretch gap-8 pb-50"
>
{/* Title */}
<div class="flex flex-col items-start gap-2 self-stretch sticky top-0 bg-background-stronger z-10 pb-1">
<div class="w-full text-14-medium text-text-strong">
<Show
when={titled()}
fallback={
<Typewriter
as="h1"
text={message.summary?.title}
class="overflow-hidden text-ellipsis min-w-0 text-nowrap"
/>
}
>
<h1 class="overflow-hidden text-ellipsis min-w-0 text-nowrap">
{message.summary?.title}
</h1>
</Show>
</div>
</div>
<div class="-mt-9">
<Message message={message} parts={parts()} />
</div>
{/* Summary */}
<Show when={completed()}>
<div class="w-full flex flex-col gap-6 items-start self-stretch">
<div class="flex flex-col items-start gap-1 self-stretch">
<h2 class="text-12-medium text-text-weak">
<Switch>
<Match when={message.summary?.diffs?.length}>Summary</Match>
<Match when={true}>Response</Match>
</Switch>
</h2>
<Show when={message.summary?.body}>
{(summary) => (
<Markdown
classList={{
"text-14-regular": !!message.summary?.diffs?.length,
"[&>*]:fade-up-text": !message.summary?.diffs?.length,
}}
text={summary()}
/>
)}
</Show>
</div>
<Accordion class="w-full" multiple>
<For each={message.summary?.diffs ?? []}>
{(diff) => (
<Accordion.Item value={diff.file}>
<Accordion.Header>
<Accordion.Trigger>
<div class="flex items-center justify-between w-full gap-5">
<div class="grow flex items-center gap-5 min-w-0">
<FileIcon
node={{ path: diff.file, type: "file" }}
class="shrink-0 size-4"
/>
<div class="flex grow min-w-0">
<Show when={diff.file.includes("/")}>
<span class="text-text-base truncate-start">
{getDirectory(diff.file)}&lrm;
</span>
</Show>
<span class="text-text-strong shrink-0">
{getFilename(diff.file)}
</span>
</div>
</div>
<div class="shrink-0 flex gap-4 items-center justify-end">
<DiffChanges changes={diff} />
<Icon name="chevron-grabber-vertical" size="small" />
</div>
</div>
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content class="max-h-[360px] overflow-y-auto no-scrollbar">
<Diff
before={{
name: diff.file!,
contents: diff.before!,
}}
after={{
name: diff.file!,
contents: diff.after!,
}}
/>
</Accordion.Content>
</Accordion.Item>
)}
</For>
</Accordion>
</div>
</Show>
<Show when={error() && !detailsExpanded()}>
<Card variant="error" class="text-text-on-critical-base">
{error()?.data?.message as string}
</Card>
</Show>
{/* Response */}
<div class="w-full">
<Switch>
<Match when={!completed()}>
<MessageProgress assistantMessages={assistantMessages} done={!working()} />
</Match>
<Match when={completed() && hasToolPart()}>
<Collapsible
variant="ghost"
open={detailsExpanded()}
onOpenChange={setDetailsExpanded}
>
<Collapsible.Trigger class="text-text-weak hover:text-text-strong">
<div class="flex items-center gap-1 self-stretch">
<div class="text-12-medium">
<Switch>
<Match when={detailsExpanded()}>Hide details</Match>
<Match when={!detailsExpanded()}>Show details</Match>
</Switch>
</div>
<Collapsible.Arrow />
</div>
</Collapsible.Trigger>
<Collapsible.Content>
<div class="w-full flex flex-col items-start self-stretch gap-3">
<For each={assistantMessages()}>
{(assistantMessage) => {
const parts = createMemo(() => sync.data.part[assistantMessage.id])
return <Message message={assistantMessage} parts={parts()} />
}}
</For>
<Show when={error()}>
<Card variant="error" class="text-text-on-critical-base">
{error()?.data?.message as string}
</Card>
</Show>
</div>
</Collapsible.Content>
</Collapsible>
</Match>
</Switch>
</div>
</div>
</Show>
)
}}
</For>
</div>
</div>
</div>
<Show when={local.layout.review.state() === "open"}>
<div
classList={{
"relative px-6 py-2 w-full flex flex-col gap-6 flex-1 min-h-0 border-l border-border-weak-base": true,
}}
>
<div class="h-8 w-full flex items-center justify-between shrink-0 self-stretch">
<div class="flex items-center gap-x-3">
<Tooltip value="Close">
<IconButton icon="align-right" variant="ghost" onClick={local.layout.review.close} />
</Tooltip>
<Tooltip value="Open in tab">
<IconButton
icon="expand"
variant="ghost"
onClick={() => {
local.layout.review.tab()
session.layout.setActiveTab("review")
}}
/>
</Tooltip>
</div>
</div>
<div class="text-14-medium text-text-strong">All changes</div>
<div class="h-full pb-40 overflow-y-auto no-scrollbar">
<Accordion class="w-full" multiple>
<For each={session.diffs()}>
{(diff) => (
<Accordion.Item value={diff.file} defaultOpen>
<Accordion.Header>
<Accordion.Trigger>
<div class="flex items-center justify-between w-full gap-5">
<div class="grow flex items-center gap-5 min-w-0">
<FileIcon node={{ path: diff.file, type: "file" }} class="shrink-0 size-4" />
<div class="flex grow min-w-0">
<Show when={diff.file.includes("/")}>
<span class="text-text-base truncate-start">
{getDirectory(diff.file)}&lrm;
</span>
</Show>
<span class="text-text-strong shrink-0">{getFilename(diff.file)}</span>
</div>
</div>
<div class="shrink-0 flex gap-4 items-center justify-end">
<DiffChanges changes={diff} />
<Icon name="chevron-grabber-vertical" size="small" />
</div>
</div>
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content>
<Diff
before={{
name: diff.file!,
contents: diff.before!,
}}
after={{
name: diff.file!,
contents: diff.after!,
}}
/>
</Accordion.Content>
</Accordion.Item>
)}
</For>
</Accordion>
</div>
</div>
</Show>
</div>
)
}}
</Show>
</Tabs.Content>
<Show when={local.layout.review.state() === "tab" && session.diffs().length}>
<Tabs.Content value="review" class="select-text">
<div
classList={{
"relative px-6 py-2 w-full flex flex-col gap-6 flex-1 min-h-0": true,
}}
>
<div class="h-8 w-full flex items-center justify-between shrink-0 self-stretch sticky top-0 bg-background-strong z-100">
<div class="flex items-center gap-x-3"></div>
</div>
<div class="text-14-medium text-text-strong">All changes</div>
<div class="h-full pb-40 overflow-y-auto no-scrollbar">
<Accordion class="w-full" multiple>
<For each={session.diffs()}>
{(diff) => (
<Accordion.Item value={diff.file} defaultOpen>
<Accordion.Header>
<Accordion.Trigger>
<div class="flex items-center justify-between w-full gap-5">
<div class="grow flex items-center gap-5 min-w-0">
<FileIcon node={{ path: diff.file, type: "file" }} class="shrink-0 size-4" />
<div class="flex grow min-w-0">
<Show when={diff.file.includes("/")}>
<span class="text-text-base truncate-start">{getDirectory(diff.file)}&lrm;</span>
</Show>
<span class="text-text-strong shrink-0">{getFilename(diff.file)}</span>
</div>
</div>
<div class="shrink-0 flex gap-4 items-center justify-end">
<DiffChanges changes={diff} />
<Icon name="chevron-grabber-vertical" size="small" />
</div>
</div>
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content>
<Diff
before={{
name: diff.file!,
contents: diff.before!,
}}
after={{
name: diff.file!,
contents: diff.after!,
}}
/>
</Accordion.Content>
</Accordion.Item>
)}
</For>
</Accordion>
</div>
</div>
</Tabs.Content>
</Show>
<For each={session.layout.tabs.opened}>
{(tab) => {
const [file] = createResource(
() => tab,
async (tab) => {
if (tab.startsWith("file://")) {
return local.file.node(tab.replace("file://", ""))
}
return undefined
},
)
return (
<Tabs.Content value={tab} class="select-text">
<Switch>
<Match when={file()}>
{(f) => (
<Code
file={{ name: f().path, contents: f().content?.content ?? "" }}
overflow="scroll"
class="pt-3 pb-40"
/>
)}
</Match>
</Switch>
</Tabs.Content>
)
}}
</For>
</Tabs>
<DragOverlay>
<Show when={store.activeDraggable}>
{(draggedFile) => {
const [file] = createResource(
() => draggedFile(),
async (tab) => {
if (tab.startsWith("file://")) {
return local.file.node(tab.replace("file://", ""))
}
return undefined
},
)
return (
<div class="relative px-3 h-10 flex items-center bg-background-base border-x border-border-weak-base border-b border-b-transparent">
<Show when={file()}>{(f) => <FileVisual file={f()} />}</Show>
</div>
)
}}
</Show>
</DragOverlay>
</DragDropProvider>
<div class="absolute inset-x-0 px-6 max-w-2xl flex flex-col justify-center items-center z-50 mx-auto bottom-8">
<PromptInput
ref={(el) => {
inputRef = el
}}
/>
</div>
<div class="hidden shrink-0 w-56 p-2 h-full overflow-y-auto">
{/* <FileTree path="" onFileClick={ handleTabClick} /> */}
</div>
<div class="hidden shrink-0 w-56 p-2">
<Show when={local.file.changes().length} fallback={<div class="px-2 text-xs text-text-muted">No changes</div>}>
<ul class="">
<For each={local.file.changes()}>
{(path) => (
<li>
<button
onClick={() => local.file.open(path, { view: "diff-unified", pinned: true })}
class="w-full flex items-center px-2 py-0.5 gap-x-2 text-text-muted grow min-w-0 hover:bg-background-element"
>
<FileIcon node={{ path, type: "file" }} class="shrink-0 size-3" />
<span class="text-xs text-text whitespace-nowrap">{getFilename(path)}</span>
<span class="text-xs text-text-muted/60 whitespace-nowrap truncate min-w-0">
{getDirectory(path)}
</span>
</button>
</li>
)}
</For>
</ul>
</Show>
</div>
<Show when={store.fileSelectOpen}>
<SelectDialog
defaultOpen
title="Select file"
placeholder="Search files"
emptyMessage="No files found"
items={local.file.searchFiles}
key={(x) => x}
onOpenChange={(open) => setStore("fileSelectOpen", open)}
onSelect={(x) => (x ? session.layout.openTab("file://" + x) : undefined)}
>
{(i) => (
<div
classList={{
"w-full flex items-center justify-between rounded-md": true,
}}
>
<div class="flex items-center gap-x-2 grow min-w-0">
<FileIcon node={{ path: i, type: "file" }} class="shrink-0 size-4" />
<div class="flex items-center text-14-regular">
<span class="text-text-weak whitespace-nowrap overflow-hidden overflow-ellipsis truncate min-w-0">
{getDirectory(i)}
</span>
<span class="text-text-strong whitespace-nowrap">{getFilename(i)}</span>
</div>
</div>
<div class="flex items-center gap-x-1 text-text-muted/40 shrink-0"></div>
</div>
)}
</SelectDialog>
</Show>
</div>
)
}
+2 -4
View File
@@ -2,9 +2,7 @@
/* tslint:disable */
/* eslint-disable */
/// <reference types="vite/client" />
interface ImportMetaEnv {
}
interface ImportMetaEnv {}
interface ImportMeta {
readonly env: ImportMetaEnv
}
}

Some files were not shown because too many files have changed in this diff Show More