refactor(core): share a browser opener across opencode and tui (#51414)
This commit is contained in:
parent
adee738d1e
commit
29f07e0c69
17 changed files with 87 additions and 36 deletions
24
bun.lock
24
bun.lock
|
|
@ -352,6 +352,7 @@
|
|||
"mime-types": "3.0.2",
|
||||
"minimatch": "10.2.5",
|
||||
"npm-package-arg": "13.0.2",
|
||||
"open": "11.0.4",
|
||||
"semver": "^7.6.3",
|
||||
"turndown": "7.2.0",
|
||||
"venice-ai-sdk-provider": "2.1.1",
|
||||
|
|
@ -645,7 +646,6 @@
|
|||
"mime-types": "3.0.2",
|
||||
"minimatch": "10.0.3",
|
||||
"npm-package-arg": "13.0.2",
|
||||
"open": "10.1.2",
|
||||
"opencode-gitlab-auth": "2.1.0",
|
||||
"opencode-poe-auth": "0.0.1",
|
||||
"opentui-spinner": "catalog:",
|
||||
|
|
@ -952,7 +952,6 @@
|
|||
"diff": "catalog:",
|
||||
"effect": "catalog:",
|
||||
"fuzzysort": "catalog:",
|
||||
"open": "10.1.2",
|
||||
"opentui-spinner": "catalog:",
|
||||
"remeda": "catalog:",
|
||||
"solid-js": "catalog:",
|
||||
|
|
@ -1086,6 +1085,7 @@
|
|||
"@opentui/solid": "catalog:",
|
||||
"@types/bun": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"open": "11.0.4",
|
||||
},
|
||||
"catalog": {
|
||||
"@cloudflare/workers-types": "4.20251008.0",
|
||||
|
|
@ -3456,7 +3456,7 @@
|
|||
|
||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||
|
||||
"default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
|
||||
"default-browser": ["default-browser@5.5.1", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw=="],
|
||||
|
||||
"default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="],
|
||||
|
||||
|
|
@ -4592,7 +4592,7 @@
|
|||
|
||||
"oniguruma-to-es": ["oniguruma-to-es@4.3.6", "", { "dependencies": { "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA=="],
|
||||
|
||||
"open": ["open@10.1.2", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "is-wsl": "^3.1.0" } }, "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw=="],
|
||||
"open": ["open@11.0.4", "", { "dependencies": { "default-browser": "^5.5.1", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.2.1", "wsl-utils": "^1.0.0" } }, "sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg=="],
|
||||
|
||||
"openai": ["openai@5.11.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-+AuTc5pVjlnTuA9zvn8rA/k+1RluPIx9AD4eDcnutv6JNwHHZxIhkFy+tmMKCvmMFDQzfA/r1ujvPWB19DQkYg=="],
|
||||
|
||||
|
|
@ -4758,7 +4758,7 @@
|
|||
|
||||
"postject": ["postject@1.0.0-alpha.6", "", { "dependencies": { "commander": "^9.4.0" }, "bin": { "postject": "dist/cli.js" } }, "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A=="],
|
||||
|
||||
"powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
|
||||
"powershell-utils": ["powershell-utils@0.2.1", "", {}, "sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A=="],
|
||||
|
||||
"preact": ["preact@11.0.0-beta.0", "", {}, "sha512-IcODoASASYwJ9kxz7+MJeiJhvLriwSb4y4mHIyxdgaRZp6kPUud7xytrk/6GZw8U3y6EFJaRb5wi9SrEK+8+lg=="],
|
||||
|
||||
|
|
@ -5550,7 +5550,7 @@
|
|||
|
||||
"ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="],
|
||||
|
||||
"wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
|
||||
"wsl-utils": ["wsl-utils@1.0.0", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA=="],
|
||||
|
||||
"xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="],
|
||||
|
||||
|
|
@ -5928,8 +5928,6 @@
|
|||
|
||||
"@hey-api/json-schema-ref-parser/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||
|
||||
"@hey-api/openapi-ts/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
|
||||
|
||||
"@hey-api/openapi-ts/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
|
||||
"@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
||||
|
|
@ -6414,8 +6412,6 @@
|
|||
|
||||
"opencode/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
|
||||
|
||||
"opencode-gitlab-auth/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
||||
|
||||
"openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
|
||||
|
||||
"openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
|
||||
|
|
@ -6488,8 +6484,6 @@
|
|||
|
||||
"sst/jose": ["jose@5.2.3", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="],
|
||||
|
||||
"storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
||||
|
||||
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
|
@ -6560,6 +6554,8 @@
|
|||
|
||||
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"wsl-utils/powershell-utils": ["powershell-utils@0.1.0", "", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="],
|
||||
|
||||
"yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="],
|
||||
|
||||
"yaml-language-server/yaml": ["yaml@2.7.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="],
|
||||
|
|
@ -7172,8 +7168,6 @@
|
|||
|
||||
"motion/framer-motion/motion-utils": ["motion-utils@12.39.0", "", {}, "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ=="],
|
||||
|
||||
"opencode-gitlab-auth/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
||||
|
||||
"opencode/@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.54", "", { "dependencies": { "@ai-sdk/provider": "3.0.12", "@ai-sdk/provider-utils": "4.0.33" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OyXt0zK8y2/ZIyWlbxTv2r1M7AK227S+Gl4BYOEF42q0wz1n5m4fwR8L4Fy/MQ4Ho6xje47MPsFcRdIqIyP6Rw=="],
|
||||
|
||||
"opencode/@ai-sdk/cerebras/@ai-sdk/provider": ["@ai-sdk/provider@3.0.12", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-sj9DWTJ2Ze0WR9qsiOPqoqzNx3OxL6iMxHImbhvoe9qOspekbzxNDMiJ4TIGfYHYh9w4OmBjz3prvqhzTi96+Q=="],
|
||||
|
|
@ -7200,8 +7194,6 @@
|
|||
|
||||
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
||||
|
||||
"storybook/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
||||
|
||||
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"tw-to-css/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
|
||||
|
|
|
|||
|
|
@ -142,7 +142,8 @@
|
|||
"@opentui/keymap": "catalog:",
|
||||
"@opentui/solid": "catalog:",
|
||||
"@types/bun": "catalog:",
|
||||
"@types/node": "catalog:"
|
||||
"@types/node": "catalog:",
|
||||
"open": "11.0.4"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@dnd-kit/dom@0.5.0": "patches/@dnd-kit%2Fdom@0.5.0.patch",
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@
|
|||
"mime-types": "3.0.2",
|
||||
"minimatch": "10.2.5",
|
||||
"npm-package-arg": "13.0.2",
|
||||
"open": "11.0.4",
|
||||
"semver": "^7.6.3",
|
||||
"turndown": "7.2.0",
|
||||
"venice-ai-sdk-provider": "2.1.1",
|
||||
|
|
|
|||
8
packages/core/src/open.ts
Normal file
8
packages/core/src/open.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import open from "open"
|
||||
|
||||
export function openUrl(input: string) {
|
||||
const url = URL.canParse(input) ? new URL(input) : undefined
|
||||
if (!url || (url.protocol !== "http:" && url.protocol !== "https:"))
|
||||
return Promise.reject(new Error(`Only http and https links can be opened in the browser: ${input}`))
|
||||
return open(url.href)
|
||||
}
|
||||
16
packages/core/test/open.test.ts
Normal file
16
packages/core/test/open.test.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { openUrl } from "@opencode-ai/core/open"
|
||||
|
||||
describe("openUrl", () => {
|
||||
test("rejects values that are not URLs", async () => {
|
||||
await expect(openUrl("not a url")).rejects.toThrow("Only http and https links")
|
||||
await expect(openUrl("")).rejects.toThrow("Only http and https links")
|
||||
})
|
||||
|
||||
test("rejects non-http schemes", async () => {
|
||||
await expect(openUrl("file:///etc/hosts")).rejects.toThrow("Only http and https links")
|
||||
await expect(openUrl("javascript:alert(1)")).rejects.toThrow("Only http and https links")
|
||||
await expect(openUrl("ms-msdt:/id PCWDiagnostic")).rejects.toThrow("Only http and https links")
|
||||
await expect(openUrl("\\\\server\\share\\file.html")).rejects.toThrow("Only http and https links")
|
||||
})
|
||||
})
|
||||
|
|
@ -131,7 +131,6 @@
|
|||
"mime-types": "3.0.2",
|
||||
"minimatch": "10.0.3",
|
||||
"npm-package-arg": "13.0.2",
|
||||
"open": "10.1.2",
|
||||
"opencode-gitlab-auth": "2.1.0",
|
||||
"opencode-poe-auth": "0.0.1",
|
||||
"opentui-spinner": "catalog:",
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import { Account } from "@/account/account"
|
|||
import { AccountID, OrgID, PollExpired, type PollResult, type AccountError } from "@/account/schema"
|
||||
import { effectCmd } from "../effect-cmd"
|
||||
import * as Prompt from "../effect/prompt"
|
||||
import open from "open"
|
||||
import { openUrl } from "@opencode-ai/core/open"
|
||||
|
||||
const openBrowser = (url: string) => Effect.promise(() => open(url).catch(() => undefined))
|
||||
const openBrowser = (url: string) => Effect.promise(() => openUrl(url).catch(() => undefined))
|
||||
|
||||
const println = (msg: string) => Effect.sync(() => UI.println(msg))
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { UI } from "../ui"
|
|||
import { effectCmd } from "../effect-cmd"
|
||||
import { withNetworkOptions, resolveNetworkOptions } from "../network"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import open from "open"
|
||||
import { openUrl } from "@opencode-ai/core/open"
|
||||
import { networkInterfaces } from "os"
|
||||
|
||||
function getNetworkIPs() {
|
||||
|
|
@ -72,11 +72,11 @@ export const WebCommand = effectCmd({
|
|||
}
|
||||
|
||||
// Open localhost in browser
|
||||
open(localhostUrl).catch(() => {})
|
||||
openUrl(localhostUrl).catch(() => {})
|
||||
} else {
|
||||
const displayUrl = server.url.toString()
|
||||
UI.println(UI.Style.TEXT_INFO_BOLD + " Web interface: ", UI.Style.TEXT_NORMAL, displayUrl)
|
||||
open(displayUrl).catch(() => {})
|
||||
openUrl(displayUrl).catch(() => {})
|
||||
}
|
||||
|
||||
yield* Effect.never
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { openUrl } from "@opencode-ai/core/open"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import open from "open"
|
||||
|
||||
export interface Interface {
|
||||
readonly open: (url: string) => Effect.Effect<void, Error>
|
||||
|
|
@ -13,7 +13,7 @@ const layer = Layer.succeed(
|
|||
Service.of({
|
||||
open: Effect.fn("McpBrowser.open")(function* (url: string) {
|
||||
const subprocess = yield* Effect.tryPromise({
|
||||
try: () => open(url),
|
||||
try: () => openUrl(url),
|
||||
catch: (error) => (error instanceof Error ? error : new Error(String(error))),
|
||||
})
|
||||
yield* Effect.callback<void, Error>((resume) => {
|
||||
|
|
|
|||
|
|
@ -125,6 +125,10 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|||
}
|
||||
|
||||
async redirectToAuthorization(authorizationUrl: URL): Promise<void> {
|
||||
if (authorizationUrl.protocol !== "http:" && authorizationUrl.protocol !== "https:")
|
||||
throw new Error(
|
||||
`MCP server "${this.serverUrl}" returned a ${authorizationUrl.protocol} authorization URL; only http and https are supported`,
|
||||
)
|
||||
await this.callbacks.onRedirect(authorizationUrl)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import type { Model } from "@opencode-ai/sdk/v2"
|
|||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { OauthCallbackPage } from "@opencode-ai/core/oauth/page"
|
||||
import { createServer } from "http"
|
||||
import open from "open"
|
||||
import { openUrl } from "@opencode-ai/core/open"
|
||||
|
||||
const DO_OAUTH_CLIENT_ID = "b1a6c5158156caac821fd1b30253ca8acb52454a48fa744420e41889cb589f82"
|
||||
const DO_AUTHORIZE_URL = "https://cloud.digitalocean.com/v1/oauth/authorize"
|
||||
|
|
@ -279,7 +279,7 @@ export async function DigitalOceanAuthPlugin(input: PluginInput): Promise<Hooks>
|
|||
const state = generateState()
|
||||
const callbackPromise = waitForOAuthCallback(state)
|
||||
const url = buildAuthorizeUrl(state)
|
||||
await open(url).catch(() => undefined)
|
||||
await openUrl(url).catch(() => undefined)
|
||||
return {
|
||||
url,
|
||||
instructions:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { OAUTH_DUMMY_KEY } from "../auth"
|
|||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { OauthCallbackPage } from "@opencode-ai/core/oauth/page"
|
||||
import { createServer } from "http"
|
||||
import open from "open"
|
||||
import { openUrl } from "@opencode-ai/core/open"
|
||||
|
||||
const OAUTH_CLIENT_ID = "LOCAL_APPLICATION"
|
||||
const OAUTH_CALLBACK_HOST = "127.0.0.1"
|
||||
|
|
@ -470,7 +470,7 @@ export async function SnowflakeCortexAuthPlugin(_input: PluginInput): Promise<Ho
|
|||
const role = (inputs.role || "").trim() || undefined
|
||||
const url = buildAuthorizeUrl(account, role, state, pkce)
|
||||
const callbackPromise = waitForOAuthCallback(account, pkce, state)
|
||||
await open(url).catch(() => undefined)
|
||||
await openUrl(url).catch(() => undefined)
|
||||
|
||||
return {
|
||||
url,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,37 @@ describe("McpOAuthProvider.redirectUrl", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("McpOAuthProvider.redirectToAuthorization", () => {
|
||||
test("forwards http and https authorization URLs", async () => {
|
||||
const seen: string[] = []
|
||||
const provider = new McpOAuthProvider(
|
||||
"test-server",
|
||||
"https://mcp.example.com/mcp",
|
||||
{},
|
||||
{ onRedirect: (url) => void seen.push(url.href) },
|
||||
stubAuth,
|
||||
)
|
||||
await provider.redirectToAuthorization(new URL("https://auth.example.com/authorize?state=1"))
|
||||
await provider.redirectToAuthorization(new URL("http://127.0.0.1:8080/authorize"))
|
||||
expect(seen).toEqual(["https://auth.example.com/authorize?state=1", "http://127.0.0.1:8080/authorize"])
|
||||
})
|
||||
|
||||
test("rejects authorization URLs that are not http or https", async () => {
|
||||
const seen: string[] = []
|
||||
const provider = new McpOAuthProvider(
|
||||
"test-server",
|
||||
"https://mcp.example.com/mcp",
|
||||
{},
|
||||
{ onRedirect: (url) => void seen.push(url.href) },
|
||||
stubAuth,
|
||||
)
|
||||
await expect(provider.redirectToAuthorization(new URL("file:///tmp/authorize"))).rejects.toThrow(
|
||||
"returned a file: authorization URL",
|
||||
)
|
||||
expect(seen).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe("McpOAuthProvider.clientMetadata", () => {
|
||||
test("includes redirect_uris from redirectUrl", () => {
|
||||
const provider = makeProvider({ callbackPort: 6620 })
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@
|
|||
"diff": "catalog:",
|
||||
"effect": "catalog:",
|
||||
"fuzzysort": "catalog:",
|
||||
"open": "10.1.2",
|
||||
"opentui-spinner": "catalog:",
|
||||
"remeda": "catalog:",
|
||||
"strip-ansi": "7.1.2",
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ import { isDefaultTitle } from "./util/session"
|
|||
import { KVProvider, useKV } from "./context/kv"
|
||||
import * as Model from "./util/model"
|
||||
import { ArgsProvider, useArgs, type Args } from "./context/args"
|
||||
import open from "open"
|
||||
import { openUrl } from "@opencode-ai/core/open"
|
||||
import { PromptRefProvider, usePromptRef } from "./context/prompt"
|
||||
import { TuiConfigProvider, useTuiConfig, type TuiConfig } from "./config"
|
||||
import { createTuiApiAdapters } from "./plugin/adapters"
|
||||
|
|
@ -821,7 +821,7 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
|
|||
name: "docs.open",
|
||||
title: "Open docs",
|
||||
run: () => {
|
||||
open("https://opencode.ai/docs").catch(() => {})
|
||||
openUrl("https://opencode.ai/docs").catch(() => {})
|
||||
dialog.clear()
|
||||
},
|
||||
category: "System",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { RGBA, TextAttributes } from "@opentui/core"
|
||||
import open from "open"
|
||||
import { openUrl } from "@opencode-ai/core/open"
|
||||
import { createSignal } from "solid-js"
|
||||
import { selectedForeground, useTheme } from "../context/theme"
|
||||
import { useDialog, type DialogContext } from "../ui/dialog"
|
||||
|
|
@ -21,7 +21,7 @@ export type DialogRetryActionProps = {
|
|||
}
|
||||
|
||||
function runAction(props: DialogRetryActionProps, dialog: ReturnType<typeof useDialog>) {
|
||||
if (props.link) open(props.link).catch(() => {})
|
||||
if (props.link) openUrl(props.link).catch(() => {})
|
||||
props.onClose?.()
|
||||
dialog.clear()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { JSX } from "solid-js"
|
||||
import type { RGBA } from "@opentui/core"
|
||||
import open from "open"
|
||||
import { openUrl } from "@opencode-ai/core/open"
|
||||
|
||||
export interface LinkProps {
|
||||
href: string
|
||||
|
|
@ -25,7 +25,7 @@ export function Link(props: LinkProps) {
|
|||
width={props.width}
|
||||
wrapMode={props.wrapMode}
|
||||
onMouseUp={() => {
|
||||
open(props.href).catch(() => {})
|
||||
openUrl(props.href).catch(() => {})
|
||||
}}
|
||||
>
|
||||
{displayText}
|
||||
|
|
|
|||
Loading…
Reference in a new issue