mirror of
https://github.com/anomalyco/opencode.git
synced 2026-03-20 13:44:27 +00:00
Compare commits
4 Commits
pr-18308
...
gitlab-ver
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c968ed108 | ||
|
|
b3182149c8 | ||
|
|
d95fd77861 | ||
|
|
c32c2e8a8f |
8
.opencode/.gitignore
vendored
8
.opencode/.gitignore
vendored
@@ -1,6 +1,4 @@
|
||||
node_modules
|
||||
plans
|
||||
package.json
|
||||
plans/
|
||||
bun.lock
|
||||
.gitignore
|
||||
package-lock.json
|
||||
package.json
|
||||
package-lock.json
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
description: Translate content for a specified locale while preserving technical terms
|
||||
mode: subagent
|
||||
model: opencode/gpt-5.4
|
||||
model: opencode/gemini-3.1-pro
|
||||
---
|
||||
|
||||
You are a professional translator and localization specialist.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/// <reference path="../env.d.ts" />
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
import DESCRIPTION from "./github-pr-search.txt"
|
||||
|
||||
async function githubFetch(endpoint: string, options: RequestInit = {}) {
|
||||
const response = await fetch(`https://api.github.com${endpoint}`, {
|
||||
...options,
|
||||
@@ -22,16 +24,7 @@ interface PR {
|
||||
}
|
||||
|
||||
export default tool({
|
||||
description: `Use this tool to search GitHub pull requests by title and description.
|
||||
|
||||
This tool searches PRs in the anomalyco/opencode repository and returns LLM-friendly results including:
|
||||
- PR number and title
|
||||
- Author
|
||||
- State (open/closed/merged)
|
||||
- Labels
|
||||
- Description snippet
|
||||
|
||||
Use the query parameter to search for keywords that might appear in PR titles or descriptions.`,
|
||||
description: DESCRIPTION,
|
||||
args: {
|
||||
query: tool.schema.string().describe("Search query for PR titles and descriptions"),
|
||||
limit: tool.schema.number().describe("Maximum number of results to return").default(10),
|
||||
|
||||
10
.opencode/tool/github-pr-search.txt
Normal file
10
.opencode/tool/github-pr-search.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
Use this tool to search GitHub pull requests by title and description.
|
||||
|
||||
This tool searches PRs in the anomalyco/opencode repository and returns LLM-friendly results including:
|
||||
- PR number and title
|
||||
- Author
|
||||
- State (open/closed/merged)
|
||||
- Labels
|
||||
- Description snippet
|
||||
|
||||
Use the query parameter to search for keywords that might appear in PR titles or descriptions.
|
||||
@@ -1,5 +1,7 @@
|
||||
/// <reference path="../env.d.ts" />
|
||||
import { tool } from "@opencode-ai/plugin"
|
||||
import DESCRIPTION from "./github-triage.txt"
|
||||
|
||||
const TEAM = {
|
||||
desktop: ["adamdotdevin", "iamdavidhill", "Brendonovich", "nexxeln"],
|
||||
zen: ["fwang", "MrMushrooooom"],
|
||||
@@ -38,12 +40,7 @@ async function githubFetch(endpoint: string, options: RequestInit = {}) {
|
||||
}
|
||||
|
||||
export default tool({
|
||||
description: `Use this tool to assign and/or label a GitHub issue.
|
||||
|
||||
Choose labels and assignee using the current triage policy and ownership rules.
|
||||
Pick the most fitting labels for the issue and assign one owner.
|
||||
|
||||
If unsure, choose the team/section with the most overlap with the issue and assign a member from that team at random.`,
|
||||
description: DESCRIPTION,
|
||||
args: {
|
||||
assignee: tool.schema
|
||||
.enum(ASSIGNEES as [string, ...string[]])
|
||||
|
||||
6
.opencode/tool/github-triage.txt
Normal file
6
.opencode/tool/github-triage.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
Use this tool to assign and/or label a GitHub issue.
|
||||
|
||||
Choose labels and assignee using the current triage policy and ownership rules.
|
||||
Pick the most fitting labels for the issue and assign one owner.
|
||||
|
||||
If unsure, choose the team/section with the most overlap with the issue and assign a member from that team at random.
|
||||
274
bun.lock
274
bun.lock
@@ -325,12 +325,10 @@
|
||||
"@aws-sdk/credential-providers": "3.993.0",
|
||||
"@clack/prompts": "1.0.0-alpha.1",
|
||||
"@effect/platform-node": "catalog:",
|
||||
"@gitlab/gitlab-ai-provider": "3.6.0",
|
||||
"@gitlab/opencode-gitlab-auth": "1.3.3",
|
||||
"@hono/standard-validator": "0.1.5",
|
||||
"@hono/zod-validator": "catalog:",
|
||||
"@modelcontextprotocol/sdk": "1.25.2",
|
||||
"@npmcli/arborist": "9.4.0",
|
||||
"@octokit/graphql": "9.0.2",
|
||||
"@octokit/rest": "catalog:",
|
||||
"@openauthjs/openauth": "catalog:",
|
||||
@@ -359,6 +357,7 @@
|
||||
"drizzle-orm": "1.0.0-beta.16-ea816b6",
|
||||
"effect": "catalog:",
|
||||
"fuzzysort": "3.1.0",
|
||||
"gitlab-ai-provider": "5.2.0",
|
||||
"glob": "13.0.5",
|
||||
"google-auth-library": "10.5.0",
|
||||
"gray-matter": "4.0.3",
|
||||
@@ -405,7 +404,6 @@
|
||||
"@types/bun": "catalog:",
|
||||
"@types/cross-spawn": "6.0.6",
|
||||
"@types/mime-types": "3.0.1",
|
||||
"@types/npmcli__arborist": "6.3.3",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/turndown": "5.0.5",
|
||||
"@types/which": "3.0.4",
|
||||
@@ -1110,10 +1108,6 @@
|
||||
|
||||
"@fontsource/inter": ["@fontsource/inter@5.2.8", "", {}, "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg=="],
|
||||
|
||||
"@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="],
|
||||
|
||||
"@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.6.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-8LmcIQ86xkMtC7L4P1/QYVEC+yKMTRerfPeniaaQGalnzXKtX6iMHLjLPOL9Rxp55lOXi6ed0WrFuJzZx+fNRg=="],
|
||||
|
||||
"@gitlab/opencode-gitlab-auth": ["@gitlab/opencode-gitlab-auth@1.3.3", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-FT+KsCmAJjtqWr1YAq0MywGgL9kaLQ4apmsoowAXrPqHtoYf2i/nY10/A+L06kNj22EATeEDRpbB1NWXMto/SA=="],
|
||||
|
||||
"@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="],
|
||||
@@ -1190,8 +1184,6 @@
|
||||
|
||||
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
|
||||
|
||||
"@isaacs/string-locale-compare": ["@isaacs/string-locale-compare@1.1.0", "", {}, "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ=="],
|
||||
|
||||
"@jimp/core": ["@jimp/core@1.6.0", "", { "dependencies": { "@jimp/file-ops": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^16.0.0", "mime": "3" } }, "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w=="],
|
||||
|
||||
"@jimp/diff": ["@jimp/diff@1.6.0", "", { "dependencies": { "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "pixelmatch": "^5.3.0" } }, "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw=="],
|
||||
@@ -1364,35 +1356,9 @@
|
||||
|
||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||
|
||||
"@npm/types": ["@npm/types@1.0.2", "", {}, "sha512-KXZccTDEnWqNrrx6JjpJKU/wJvNeg9BDgjS0XhmlZab7br921HtyVbsYzJr4L+xIvjdJ20Wh9dgxgCI2a5CEQw=="],
|
||||
"@npmcli/agent": ["@npmcli/agent@3.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q=="],
|
||||
|
||||
"@npmcli/agent": ["@npmcli/agent@4.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA=="],
|
||||
|
||||
"@npmcli/arborist": ["@npmcli/arborist@9.4.0", "", { "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", "@npmcli/fs": "^5.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/map-workspaces": "^5.0.0", "@npmcli/metavuln-calculator": "^9.0.2", "@npmcli/name-from-folder": "^4.0.0", "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/query": "^5.0.0", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.0", "bin-links": "^6.0.0", "cacache": "^20.0.1", "common-ancestor-path": "^2.0.0", "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", "lru-cache": "^11.2.1", "minimatch": "^10.0.3", "nopt": "^9.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.0", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "pacote": "^21.0.2", "parse-conflict-json": "^5.0.1", "proc-log": "^6.0.0", "proggy": "^4.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", "semver": "^7.3.7", "ssri": "^13.0.0", "treeverse": "^3.0.0", "walk-up-path": "^4.0.0" }, "bin": { "arborist": "bin/index.js" } }, "sha512-4Bm8hNixJG/sii1PMnag0V9i/sGOX9VRzFrUiZMSBJpGlLR38f+Btl85d07G9GL56xO0l0OZjvrGNYsDYp0xKA=="],
|
||||
|
||||
"@npmcli/fs": ["@npmcli/fs@5.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og=="],
|
||||
|
||||
"@npmcli/git": ["@npmcli/git@7.0.2", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/promise-spawn": "^9.0.0", "ini": "^6.0.0", "lru-cache": "^11.2.1", "npm-pick-manifest": "^11.0.1", "proc-log": "^6.0.0", "semver": "^7.3.5", "which": "^6.0.0" } }, "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg=="],
|
||||
|
||||
"@npmcli/installed-package-contents": ["@npmcli/installed-package-contents@4.0.0", "", { "dependencies": { "npm-bundled": "^5.0.0", "npm-normalize-package-bin": "^5.0.0" }, "bin": { "installed-package-contents": "bin/index.js" } }, "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA=="],
|
||||
|
||||
"@npmcli/map-workspaces": ["@npmcli/map-workspaces@5.0.3", "", { "dependencies": { "@npmcli/name-from-folder": "^4.0.0", "@npmcli/package-json": "^7.0.0", "glob": "^13.0.0", "minimatch": "^10.0.3" } }, "sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw=="],
|
||||
|
||||
"@npmcli/metavuln-calculator": ["@npmcli/metavuln-calculator@9.0.3", "", { "dependencies": { "cacache": "^20.0.0", "json-parse-even-better-errors": "^5.0.0", "pacote": "^21.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5" } }, "sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg=="],
|
||||
|
||||
"@npmcli/name-from-folder": ["@npmcli/name-from-folder@4.0.0", "", {}, "sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg=="],
|
||||
|
||||
"@npmcli/node-gyp": ["@npmcli/node-gyp@5.0.0", "", {}, "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ=="],
|
||||
|
||||
"@npmcli/package-json": ["@npmcli/package-json@7.0.5", "", { "dependencies": { "@npmcli/git": "^7.0.0", "glob": "^13.0.0", "hosted-git-info": "^9.0.0", "json-parse-even-better-errors": "^5.0.0", "proc-log": "^6.0.0", "semver": "^7.5.3", "spdx-expression-parse": "^4.0.0" } }, "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ=="],
|
||||
|
||||
"@npmcli/promise-spawn": ["@npmcli/promise-spawn@9.0.1", "", { "dependencies": { "which": "^6.0.0" } }, "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q=="],
|
||||
|
||||
"@npmcli/query": ["@npmcli/query@5.0.0", "", { "dependencies": { "postcss-selector-parser": "^7.0.0" } }, "sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ=="],
|
||||
|
||||
"@npmcli/redact": ["@npmcli/redact@4.0.0", "", {}, "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q=="],
|
||||
|
||||
"@npmcli/run-script": ["@npmcli/run-script@10.0.4", "", { "dependencies": { "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "node-gyp": "^12.1.0", "proc-log": "^6.0.0" } }, "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg=="],
|
||||
"@npmcli/fs": ["@npmcli/fs@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q=="],
|
||||
|
||||
"@octokit/auth-app": ["@octokit/auth-app@8.0.1", "", { "dependencies": { "@octokit/auth-oauth-app": "^9.0.1", "@octokit/auth-oauth-user": "^6.0.0", "@octokit/request": "^10.0.2", "@octokit/request-error": "^7.0.0", "@octokit/types": "^14.0.0", "toad-cache": "^3.7.0", "universal-github-app-jwt": "^2.2.0", "universal-user-agent": "^7.0.0" } }, "sha512-P2J5pB3pjiGwtJX4WqJVYCtNkcZ+j5T2Wm14aJAEIC3WJOrv12jvBley3G1U/XI8q9o1A7QMG54LiFED2BiFlg=="],
|
||||
|
||||
@@ -1770,18 +1736,6 @@
|
||||
|
||||
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
|
||||
|
||||
"@sigstore/bundle": ["@sigstore/bundle@4.0.0", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A=="],
|
||||
|
||||
"@sigstore/core": ["@sigstore/core@3.2.0", "", {}, "sha512-kxHrDQ9YgfrWUSXU0cjsQGv8JykOFZQ9ErNKbFPWzk3Hgpwu8x2hHrQ9IdA8yl+j9RTLTC3sAF3Tdq1IQCP4oA=="],
|
||||
|
||||
"@sigstore/protobuf-specs": ["@sigstore/protobuf-specs@0.5.0", "", {}, "sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA=="],
|
||||
|
||||
"@sigstore/sign": ["@sigstore/sign@4.1.1", "", { "dependencies": { "@gar/promise-retry": "^1.0.2", "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.2.0", "@sigstore/protobuf-specs": "^0.5.0", "make-fetch-happen": "^15.0.4", "proc-log": "^6.1.0" } }, "sha512-Hf4xglukg0XXQ2RiD5vSoLjdPe8OBUPA8XeVjUObheuDcWdYWrnH/BNmxZCzkAy68MzmNCxXLeurJvs6hcP2OQ=="],
|
||||
|
||||
"@sigstore/tuf": ["@sigstore/tuf@4.0.2", "", { "dependencies": { "@sigstore/protobuf-specs": "^0.5.0", "tuf-js": "^4.1.0" } }, "sha512-TCAzTy0xzdP79EnxSjq9KQ3eaR7+FmudLC6eRKknVKZbV7ZNlGLClAAQb/HMNJ5n2OBNk2GT1tEmU0xuPr+SLQ=="],
|
||||
|
||||
"@sigstore/verify": ["@sigstore/verify@3.1.0", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag=="],
|
||||
|
||||
"@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="],
|
||||
|
||||
"@slack/bolt": ["@slack/bolt@3.22.0", "", { "dependencies": { "@slack/logger": "^4.0.0", "@slack/oauth": "^2.6.3", "@slack/socket-mode": "^1.3.6", "@slack/types": "^2.13.0", "@slack/web-api": "^6.13.0", "@types/express": "^4.16.1", "@types/promise.allsettled": "^1.0.3", "@types/tsscmp": "^1.0.0", "axios": "^1.7.4", "express": "^4.21.0", "path-to-regexp": "^8.1.0", "promise.allsettled": "^1.0.2", "raw-body": "^2.3.3", "tsscmp": "^1.0.6" } }, "sha512-iKDqGPEJDnrVwxSVlFW6OKTkijd7s4qLBeSufoBsTM0reTyfdp/5izIQVkxNfzjHi3o6qjdYbRXkYad5HBsBog=="],
|
||||
@@ -2082,10 +2036,6 @@
|
||||
|
||||
"@tsconfig/node22": ["@tsconfig/node22@22.0.2", "", {}, "sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA=="],
|
||||
|
||||
"@tufjs/canonical-json": ["@tufjs/canonical-json@2.0.0", "", {}, "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA=="],
|
||||
|
||||
"@tufjs/models": ["@tufjs/models@4.1.0", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^10.1.1" } }, "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww=="],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
|
||||
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
|
||||
@@ -2104,8 +2054,6 @@
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
||||
|
||||
"@types/cacache": ["@types/cacache@20.0.1", "", { "dependencies": { "@types/node": "*", "minipass": "*" } }, "sha512-QlKW3AFoFr/hvPHwFHMIVUH/ZCYeetBNou3PCmxu5LaNDvrtBlPJtIA6uhmU9JRt9oxj7IYoqoLcpxtzpPiTcw=="],
|
||||
|
||||
"@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="],
|
||||
|
||||
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
|
||||
@@ -2170,18 +2118,6 @@
|
||||
|
||||
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
|
||||
|
||||
"@types/npm-package-arg": ["@types/npm-package-arg@6.1.4", "", {}, "sha512-vDgdbMy2QXHnAruzlv68pUtXCjmqUk3WrBAsRboRovsOmxbfn/WiYCjmecyKjGztnMps5dWp4Uq2prp+Ilo17Q=="],
|
||||
|
||||
"@types/npm-registry-fetch": ["@types/npm-registry-fetch@8.0.9", "", { "dependencies": { "@types/node": "*", "@types/node-fetch": "*", "@types/npm-package-arg": "*", "@types/npmlog": "*", "@types/ssri": "*" } }, "sha512-7NxvodR5Yrop3pb6+n8jhJNyzwOX0+6F+iagNEoi9u1CGxruYAwZD8pvGc9prIkL0+FdX5Xp0p80J9QPrGUp/g=="],
|
||||
|
||||
"@types/npmcli__arborist": ["@types/npmcli__arborist@6.3.3", "", { "dependencies": { "@npm/types": "^1", "@types/cacache": "*", "@types/node": "*", "@types/npmcli__package-json": "*", "@types/pacote": "*" } }, "sha512-kyrX932Qr+/Y4OB47Jamgc2YWa/HlXTCN0KVJsq04XDHUGkfbprJA8rd66zZXHmHmvnz1LR4X17zsE/H8Mklew=="],
|
||||
|
||||
"@types/npmcli__package-json": ["@types/npmcli__package-json@4.0.4", "", {}, "sha512-6QjlFUSHBmZJWuC08bz1ZCx6tm4t+7+OJXAdvM6tL2pI7n6Bh5SIp/YxQvnOLFf8MzCXs2ijyFgrzaiu1UFBGA=="],
|
||||
|
||||
"@types/npmlog": ["@types/npmlog@7.0.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-hJWbrKFvxKyWwSUXjZMYTINsSOY6IclhvGOZ97M8ac2tmR9hMwmTnYaMdpGhvju9ctWLTPhCS+eLfQNluiEjQQ=="],
|
||||
|
||||
"@types/pacote": ["@types/pacote@11.1.8", "", { "dependencies": { "@types/node": "*", "@types/npm-registry-fetch": "*", "@types/npmlog": "*", "@types/ssri": "*" } }, "sha512-/XLR0VoTh2JEO0jJg1q/e6Rh9bxjBq9vorJuQmtT7rRrXSiWz7e7NsvXVYJQ0i8JxMlBMPPYDTnrRe7MZRFA8Q=="],
|
||||
|
||||
"@types/plist": ["@types/plist@3.0.5", "", { "dependencies": { "@types/node": "*", "xmlbuilder": ">=11.0.1" } }, "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA=="],
|
||||
|
||||
"@types/promise.allsettled": ["@types/promise.allsettled@1.0.6", "", {}, "sha512-wA0UT0HeT2fGHzIFV9kWpYz5mdoyLxKrTgMdZQM++5h6pYAFH73HXcQhefg24nD1yivUFEn5KU+EF4b+CXJ4Wg=="],
|
||||
@@ -2210,8 +2146,6 @@
|
||||
|
||||
"@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
|
||||
|
||||
"@types/ssri": ["@types/ssri@7.1.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-odD/56S3B51liILSk5aXJlnYt99S6Rt9EFDDqGtJM26rKHApHcwyU/UoYHrzKkdkHMAIquGWCuHtQTbes+FRQw=="],
|
||||
|
||||
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
|
||||
|
||||
"@types/tsscmp": ["@types/tsscmp@1.0.2", "", {}, "sha512-cy7BRSU8GYYgxjcx0Py+8lo5MthuDhlyu076KUcYzVNXL23luYgRHkMG2fIFEc6neckeh/ntP82mw+U4QjZq+g=="],
|
||||
@@ -2298,7 +2232,7 @@
|
||||
|
||||
"@zip.js/zip.js": ["@zip.js/zip.js@2.7.62", "", {}, "sha512-OaLvZ8j4gCkLn048ypkZu29KX30r8/OfFF2w4Jo5WXFr+J04J+lzJ5TKZBVgFXhlvSkqNFQdfnY1Q8TMTCyBVA=="],
|
||||
|
||||
"abbrev": ["abbrev@4.0.0", "", {}, "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA=="],
|
||||
"abbrev": ["abbrev@2.0.0", "", {}, "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="],
|
||||
|
||||
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
|
||||
|
||||
@@ -2462,8 +2396,6 @@
|
||||
|
||||
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
|
||||
|
||||
"bin-links": ["bin-links@6.0.0", "", { "dependencies": { "cmd-shim": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "proc-log": "^6.0.0", "read-cmd-shim": "^6.0.0", "write-file-atomic": "^7.0.0" } }, "sha512-X4CiKlcV2GjnCMwnKAfbVWpHa++65th9TuzAEYtZoATiOE2DQKhSp4CJlyLoTqdhBKlXjpXjCTYPNNFS33Fi6w=="],
|
||||
|
||||
"binary": ["binary@0.3.0", "", { "dependencies": { "buffers": "~0.1.1", "chainsaw": "~0.1.0" } }, "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg=="],
|
||||
|
||||
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
|
||||
@@ -2536,7 +2468,7 @@
|
||||
|
||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||
|
||||
"cacache": ["cacache@20.0.4", "", { "dependencies": { "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", "glob": "^13.0.0", "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^13.0.0" } }, "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA=="],
|
||||
"cacache": ["cacache@19.0.1", "", { "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "tar": "^7.4.3", "unique-filename": "^4.0.0" } }, "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ=="],
|
||||
|
||||
"cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="],
|
||||
|
||||
@@ -2616,8 +2548,6 @@
|
||||
|
||||
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
|
||||
|
||||
"cmd-shim": ["cmd-shim@8.0.0", "", {}, "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA=="],
|
||||
|
||||
"collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="],
|
||||
|
||||
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
|
||||
@@ -3096,6 +3026,8 @@
|
||||
|
||||
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
|
||||
|
||||
"gitlab-ai-provider": ["gitlab-ai-provider@5.2.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-7uUbITj7tqNF+AG4AgVEYpkIsXGCCt0BLHULghaXktyP7DOqqMYc3967AnbYZQW04uC8MXeAxCCaaWZ/frwk3A=="],
|
||||
|
||||
"glob": ["glob@13.0.5", "", { "dependencies": { "minimatch": "^10.2.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-BzXxZg24Ibra1pbQ/zE7Kys4Ua1ks7Bn6pKLkVPZ9FZe4JQS6/Q7ef3LG1H+k7lUf5l4T3PLSyYyYJVYUvfgTw=="],
|
||||
|
||||
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
@@ -3194,7 +3126,7 @@
|
||||
|
||||
"hono-openapi": ["hono-openapi@1.1.2", "", { "peerDependencies": { "@hono/standard-validator": "^0.2.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-toUcO60MftRBxqcVyxsHNYs2m4vf4xkQaiARAucQx3TiBPDtMNNkoh+C4I1vAretQZiGyaLOZNWn1YxfSyUA5g=="],
|
||||
|
||||
"hosted-git-info": ["hosted-git-info@9.0.2", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg=="],
|
||||
"hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="],
|
||||
|
||||
"html-entities": ["html-entities@2.3.3", "", {}, "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA=="],
|
||||
|
||||
@@ -3238,8 +3170,6 @@
|
||||
|
||||
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"ignore-walk": ["ignore-walk@8.0.0", "", { "dependencies": { "minimatch": "^10.0.3" } }, "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A=="],
|
||||
|
||||
"image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="],
|
||||
|
||||
"import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="],
|
||||
@@ -3412,8 +3342,6 @@
|
||||
|
||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||
|
||||
"json-parse-even-better-errors": ["json-parse-even-better-errors@5.0.0", "", {}, "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ=="],
|
||||
|
||||
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
|
||||
|
||||
"json-schema-ref-resolver": ["json-schema-ref-resolver@3.0.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A=="],
|
||||
@@ -3424,8 +3352,6 @@
|
||||
|
||||
"json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
|
||||
|
||||
"json-stringify-nice": ["json-stringify-nice@1.1.4", "", {}, "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw=="],
|
||||
|
||||
"json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="],
|
||||
|
||||
"json-with-bigint": ["json-with-bigint@3.5.7", "", {}, "sha512-7ei3MdAI5+fJPVnKlW77TKNKwQ5ppSzWvhPuSuINT/GYW9ZOC1eRKOuhV9yHG5aEsUPj9BBx5JIekkmoLHxZOw=="],
|
||||
@@ -3436,14 +3362,8 @@
|
||||
|
||||
"jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
|
||||
|
||||
"jsonparse": ["jsonparse@1.3.1", "", {}, "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg=="],
|
||||
|
||||
"jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="],
|
||||
|
||||
"just-diff": ["just-diff@6.0.2", "", {}, "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA=="],
|
||||
|
||||
"just-diff-apply": ["just-diff-apply@5.5.0", "", {}, "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw=="],
|
||||
|
||||
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
|
||||
|
||||
"jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
|
||||
@@ -3556,7 +3476,7 @@
|
||||
|
||||
"magicast": ["magicast@0.3.5", "", { "dependencies": { "@babel/parser": "^7.25.4", "@babel/types": "^7.25.4", "source-map-js": "^1.2.0" } }, "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ=="],
|
||||
|
||||
"make-fetch-happen": ["make-fetch-happen@15.0.5", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "@npmcli/redact": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", "ssri": "^13.0.0" } }, "sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg=="],
|
||||
"make-fetch-happen": ["make-fetch-happen@14.0.3", "", { "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" } }, "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ=="],
|
||||
|
||||
"markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="],
|
||||
|
||||
@@ -3722,13 +3642,13 @@
|
||||
|
||||
"minipass-collect": ["minipass-collect@2.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw=="],
|
||||
|
||||
"minipass-fetch": ["minipass-fetch@5.0.2", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^2.0.0", "minizlib": "^3.0.1" }, "optionalDependencies": { "iconv-lite": "^0.7.2" } }, "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ=="],
|
||||
"minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="],
|
||||
|
||||
"minipass-flush": ["minipass-flush@1.0.5", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw=="],
|
||||
|
||||
"minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="],
|
||||
|
||||
"minipass-sized": ["minipass-sized@2.0.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA=="],
|
||||
"minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="],
|
||||
|
||||
"minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="],
|
||||
|
||||
@@ -3796,7 +3716,7 @@
|
||||
|
||||
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||
|
||||
"node-gyp": ["node-gyp@12.2.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^15.0.0", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ=="],
|
||||
"node-gyp": ["node-gyp@11.5.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ=="],
|
||||
|
||||
"node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="],
|
||||
|
||||
@@ -3808,26 +3728,12 @@
|
||||
|
||||
"node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="],
|
||||
|
||||
"nopt": ["nopt@9.0.0", "", { "dependencies": { "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw=="],
|
||||
"nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="],
|
||||
|
||||
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
|
||||
|
||||
"normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="],
|
||||
|
||||
"npm-bundled": ["npm-bundled@5.0.0", "", { "dependencies": { "npm-normalize-package-bin": "^5.0.0" } }, "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw=="],
|
||||
|
||||
"npm-install-checks": ["npm-install-checks@8.0.0", "", { "dependencies": { "semver": "^7.1.1" } }, "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA=="],
|
||||
|
||||
"npm-normalize-package-bin": ["npm-normalize-package-bin@5.0.0", "", {}, "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag=="],
|
||||
|
||||
"npm-package-arg": ["npm-package-arg@13.0.2", "", { "dependencies": { "hosted-git-info": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^7.0.0" } }, "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA=="],
|
||||
|
||||
"npm-packlist": ["npm-packlist@10.0.4", "", { "dependencies": { "ignore-walk": "^8.0.0", "proc-log": "^6.0.0" } }, "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng=="],
|
||||
|
||||
"npm-pick-manifest": ["npm-pick-manifest@11.0.3", "", { "dependencies": { "npm-install-checks": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "npm-package-arg": "^13.0.0", "semver": "^7.3.5" } }, "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ=="],
|
||||
|
||||
"npm-registry-fetch": ["npm-registry-fetch@19.1.1", "", { "dependencies": { "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", "minipass-fetch": "^5.0.0", "minizlib": "^3.0.1", "npm-package-arg": "^13.0.0", "proc-log": "^6.0.0" } }, "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw=="],
|
||||
|
||||
"npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="],
|
||||
|
||||
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
|
||||
@@ -3912,8 +3818,6 @@
|
||||
|
||||
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
|
||||
|
||||
"pacote": ["pacote@21.5.0", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "@npmcli/run-script": "^10.0.0", "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^13.0.0", "npm-packlist": "^10.0.1", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" } }, "sha512-VtZ0SB8mb5Tzw3dXDfVAIjhyVKUHZkS/ZH9/5mpKenwC9sFOXNI0JI7kEF7IMkwOnsWMFrvAZHzx1T5fmrp9FQ=="],
|
||||
|
||||
"pagefind": ["pagefind@1.4.0", "", { "optionalDependencies": { "@pagefind/darwin-arm64": "1.4.0", "@pagefind/darwin-x64": "1.4.0", "@pagefind/freebsd-x64": "1.4.0", "@pagefind/linux-arm64": "1.4.0", "@pagefind/linux-x64": "1.4.0", "@pagefind/windows-x64": "1.4.0" }, "bin": { "pagefind": "lib/runner/bin.cjs" } }, "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g=="],
|
||||
|
||||
"pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
|
||||
@@ -3926,8 +3830,6 @@
|
||||
|
||||
"parse-bmfont-xml": ["parse-bmfont-xml@1.1.6", "", { "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.5.0" } }, "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA=="],
|
||||
|
||||
"parse-conflict-json": ["parse-conflict-json@5.0.1", "", { "dependencies": { "json-parse-even-better-errors": "^5.0.0", "just-diff": "^6.0.0", "just-diff-apply": "^5.2.0" } }, "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ=="],
|
||||
|
||||
"parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
|
||||
|
||||
"parse-latin": ["parse-latin@7.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "@types/unist": "^3.0.0", "nlcst-to-string": "^4.0.0", "unist-util-modify-children": "^4.0.0", "unist-util-visit-children": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="],
|
||||
@@ -4042,7 +3944,7 @@
|
||||
|
||||
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
|
||||
|
||||
"proc-log": ["proc-log@6.1.0", "", {}, "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ=="],
|
||||
"proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="],
|
||||
|
||||
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
|
||||
|
||||
@@ -4050,14 +3952,8 @@
|
||||
|
||||
"process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
|
||||
|
||||
"proggy": ["proggy@4.0.0", "", {}, "sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ=="],
|
||||
|
||||
"progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
|
||||
|
||||
"promise-all-reject-late": ["promise-all-reject-late@1.0.1", "", {}, "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw=="],
|
||||
|
||||
"promise-call-limit": ["promise-call-limit@3.0.2", "", {}, "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw=="],
|
||||
|
||||
"promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="],
|
||||
|
||||
"promise.allsettled": ["promise.allsettled@1.0.7", "", { "dependencies": { "array.prototype.map": "^1.0.5", "call-bind": "^1.0.2", "define-properties": "^1.2.0", "es-abstract": "^1.22.1", "get-intrinsic": "^1.2.1", "iterate-value": "^1.0.2" } }, "sha512-hezvKvQQmsFkOdrZfYxUxkyxl8mgFQeT259Ajj9PXdbg9VzBCWrItOev72JyWxkCD5VSSqAeHmlN3tWx4DlmsA=="],
|
||||
@@ -4124,8 +4020,6 @@
|
||||
|
||||
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
|
||||
|
||||
"read-cmd-shim": ["read-cmd-shim@6.0.0", "", {}, "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A=="],
|
||||
|
||||
"readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
|
||||
|
||||
"readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="],
|
||||
@@ -4326,8 +4220,6 @@
|
||||
|
||||
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"sigstore": ["sigstore@4.1.0", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0", "@sigstore/sign": "^4.1.0", "@sigstore/tuf": "^4.0.1", "@sigstore/verify": "^3.1.0" } }, "sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA=="],
|
||||
|
||||
"simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="],
|
||||
|
||||
"simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="],
|
||||
@@ -4378,12 +4270,6 @@
|
||||
|
||||
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
||||
|
||||
"spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="],
|
||||
|
||||
"spdx-expression-parse": ["spdx-expression-parse@4.0.0", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ=="],
|
||||
|
||||
"spdx-license-ids": ["spdx-license-ids@3.0.23", "", {}, "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw=="],
|
||||
|
||||
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||
|
||||
"sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="],
|
||||
@@ -4392,7 +4278,7 @@
|
||||
|
||||
"srvx": ["srvx@0.9.8", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-RZaxTKJEE/14HYn8COLuUOJAt0U55N9l1Xf6jj+T0GoA01EUH1Xz5JtSUOI+EHn+AEgPCVn7gk6jHJffrr06fQ=="],
|
||||
|
||||
"ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="],
|
||||
"ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="],
|
||||
|
||||
"sst": ["sst@3.18.10", "", { "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.18.10", "sst-darwin-x64": "3.18.10", "sst-linux-arm64": "3.18.10", "sst-linux-x64": "3.18.10", "sst-linux-x86": "3.18.10", "sst-win32-arm64": "3.18.10", "sst-win32-x64": "3.18.10", "sst-win32-x86": "3.18.10" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-SY+ldeJ9K5E9q+DhjXA3e2W3BEOzBwkE3IyLSD71uA3/5nRhUAST31iOWEpW36LbIvSQ9uOVDFcebztoLJ8s7w=="],
|
||||
|
||||
@@ -4568,8 +4454,6 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"treeverse": ["treeverse@3.0.0", "", {}, "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ=="],
|
||||
|
||||
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
|
||||
|
||||
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
|
||||
@@ -4588,8 +4472,6 @@
|
||||
|
||||
"tsscmp": ["tsscmp@1.0.6", "", {}, "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA=="],
|
||||
|
||||
"tuf-js": ["tuf-js@4.1.0", "", { "dependencies": { "@tufjs/models": "4.1.0", "debug": "^4.4.3", "make-fetch-happen": "^15.0.1" } }, "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ=="],
|
||||
|
||||
"tunnel": ["tunnel@0.0.6", "", {}, "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="],
|
||||
|
||||
"turbo": ["turbo@2.8.13", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.13", "turbo-darwin-arm64": "2.8.13", "turbo-linux-64": "2.8.13", "turbo-linux-arm64": "2.8.13", "turbo-windows-64": "2.8.13", "turbo-windows-arm64": "2.8.13" }, "bin": { "turbo": "bin/turbo" } }, "sha512-nyM99hwFB9/DHaFyKEqatdayGjsMNYsQ/XBNO6MITc7roncZetKb97MpHxWf3uiU+LB9c9HUlU3Jp2Ixei2k1A=="],
|
||||
@@ -4716,8 +4598,6 @@
|
||||
|
||||
"uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="],
|
||||
|
||||
"validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="],
|
||||
|
||||
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||
|
||||
"verror": ["verror@1.10.1", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg=="],
|
||||
@@ -4776,8 +4656,6 @@
|
||||
|
||||
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
|
||||
|
||||
"walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="],
|
||||
|
||||
"wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
|
||||
|
||||
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
|
||||
@@ -4822,8 +4700,6 @@
|
||||
|
||||
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||
|
||||
"write-file-atomic": ["write-file-atomic@7.0.1", "", { "dependencies": { "signal-exit": "^4.0.1" } }, "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg=="],
|
||||
|
||||
"ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
|
||||
|
||||
"wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
|
||||
@@ -5168,8 +5044,6 @@
|
||||
|
||||
"@electron/rebuild/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"@electron/rebuild/node-gyp": ["node-gyp@11.5.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ=="],
|
||||
|
||||
"@electron/rebuild/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
|
||||
|
||||
"@electron/universal/fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="],
|
||||
@@ -5180,10 +5054,6 @@
|
||||
|
||||
"@fastify/proxy-addr/ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="],
|
||||
|
||||
"@gitlab/gitlab-ai-provider/openai": ["openai@6.27.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-osTKySlrdYrLYTt0zjhY8yp0JUBmWDCN+Q+QxsV4xMQnnoVFpylgKGgxwN8sSdTNw0G4y+WUXs4eCMWpyDNWZQ=="],
|
||||
|
||||
"@gitlab/gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@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=="],
|
||||
@@ -5246,13 +5116,7 @@
|
||||
|
||||
"@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
|
||||
|
||||
"@npmcli/arborist/common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="],
|
||||
|
||||
"@npmcli/arborist/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
|
||||
|
||||
"@npmcli/map-workspaces/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
|
||||
|
||||
"@npmcli/query/postcss-selector-parser": ["postcss-selector-parser@7.1.1", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg=="],
|
||||
"@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"@octokit/auth-app/@octokit/request": ["@octokit/request@10.0.8", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw=="],
|
||||
|
||||
@@ -5420,8 +5284,6 @@
|
||||
|
||||
"@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
|
||||
|
||||
"@tufjs/models/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
|
||||
|
||||
"@types/plist/xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
|
||||
|
||||
"@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="],
|
||||
@@ -5454,8 +5316,6 @@
|
||||
|
||||
"app-builder-lib/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="],
|
||||
|
||||
"app-builder-lib/hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="],
|
||||
|
||||
"app-builder-lib/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
|
||||
|
||||
"app-builder-lib/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
|
||||
@@ -5500,6 +5360,10 @@
|
||||
|
||||
"c12/dotenv": ["dotenv@17.3.1", "", {}, "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA=="],
|
||||
|
||||
"cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
|
||||
"cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"cli-truncate/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="],
|
||||
@@ -5584,6 +5448,10 @@
|
||||
|
||||
"gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
||||
|
||||
"gitlab-ai-provider/openai": ["openai@6.27.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-osTKySlrdYrLYTt0zjhY8yp0JUBmWDCN+Q+QxsV4xMQnnoVFpylgKGgxwN8sSdTNw0G4y+WUXs4eCMWpyDNWZQ=="],
|
||||
|
||||
"gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
|
||||
|
||||
"globby/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
@@ -5592,6 +5460,8 @@
|
||||
|
||||
"happy-dom/ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
|
||||
|
||||
"hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
|
||||
|
||||
"html-minifier-terser/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="],
|
||||
|
||||
"html-minifier-terser/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
@@ -5600,12 +5470,8 @@
|
||||
|
||||
"iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="],
|
||||
|
||||
"ignore-walk/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
|
||||
|
||||
"js-beautify/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
|
||||
"js-beautify/nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="],
|
||||
|
||||
"katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
|
||||
|
||||
"lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||
@@ -5636,12 +5502,18 @@
|
||||
|
||||
"minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
||||
|
||||
"minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
||||
|
||||
"motion/framer-motion": ["framer-motion@12.35.2", "", { "dependencies": { "motion-dom": "^12.35.2", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-dhfuEMaNo0hc+AEqyHiIfiJRNb9U9UQutE9FoKm5pjf7CMitp9xPEF1iWZihR1q86LBmo6EJ7S8cN8QXEy49AA=="],
|
||||
|
||||
"mssql/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
|
||||
|
||||
"nitro/h3": ["h3@2.0.1-rc.5", "", { "dependencies": { "rou3": "^0.7.9", "srvx": "^0.9.1" }, "peerDependencies": { "crossws": "^0.4.1" }, "optionalPeers": ["crossws"] }, "sha512-qkohAzCab0nLzXNm78tBjZDvtKMTmtygS8BJLT3VPczAQofdqlFXDPkXdLMJN4r05+xqneG8snZJ0HgkERCZTg=="],
|
||||
|
||||
"node-gyp/nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="],
|
||||
|
||||
"node-gyp/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
|
||||
|
||||
"node-gyp-build-optional-packages/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
||||
@@ -6078,14 +5950,6 @@
|
||||
|
||||
"@electron/notarize/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen": ["make-fetch-happen@14.0.3", "", { "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" } }, "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/nopt": ["nopt@8.1.0", "", { "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
|
||||
|
||||
"@electron/rebuild/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"@electron/rebuild/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
@@ -6332,8 +6196,6 @@
|
||||
|
||||
"app-builder-lib/@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"app-builder-lib/hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
|
||||
|
||||
"app-builder-lib/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
|
||||
|
||||
"archiver-utils/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
@@ -6360,6 +6222,12 @@
|
||||
|
||||
"c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
||||
|
||||
"cacache/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
|
||||
"cacache/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
|
||||
|
||||
"cacache/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=="],
|
||||
|
||||
"cli-truncate/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"cli-truncate/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
@@ -6400,14 +6268,16 @@
|
||||
|
||||
"js-beautify/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=="],
|
||||
|
||||
"js-beautify/nopt/abbrev": ["abbrev@2.0.0", "", {}, "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="],
|
||||
|
||||
"lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
||||
|
||||
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
|
||||
"motion/framer-motion/motion-dom": ["motion-dom@12.35.2", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-pWXFMTwvGDbx1Fe9YL5HZebv2NhvGBzRtiNUv58aoK7+XrsuaydQ0JGRKK2r+bTKlwgSWwWxHbP5249Qr/BNpg=="],
|
||||
|
||||
"node-gyp/nopt/abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="],
|
||||
|
||||
"node-gyp/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
|
||||
|
||||
"opencode/@ai-sdk/openai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||
|
||||
"opencode/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.20", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ=="],
|
||||
@@ -6578,20 +6448,6 @@
|
||||
|
||||
"@electron/asar/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/@npmcli/agent": ["@npmcli/agent@3.0.0", "", { "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", "socks-proxy-agent": "^8.0.3" } }, "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache": ["cacache@19.0.1", "", { "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "tar": "^7.4.3", "unique-filename": "^4.0.0" } }, "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch": ["minipass-fetch@4.0.1", "", { "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/ssri": ["ssri@12.0.0", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/nopt/abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
|
||||
|
||||
"@electron/rebuild/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@electron/rebuild/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
@@ -6708,6 +6564,10 @@
|
||||
|
||||
"babel-plugin-module-resolver/glob/path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
|
||||
|
||||
"cacache/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
"cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"dir-compare/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
@@ -6812,16 +6672,6 @@
|
||||
|
||||
"@aws-sdk/token-providers/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache/@npmcli/fs": ["@npmcli/fs@4.0.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch/minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="],
|
||||
|
||||
"@electron/rebuild/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"@electron/rebuild/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
@@ -6840,6 +6690,12 @@
|
||||
|
||||
"babel-plugin-module-resolver/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"cacache/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
|
||||
"cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
|
||||
"cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"electron-builder/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"electron-builder/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
@@ -6864,34 +6720,16 @@
|
||||
|
||||
"@aws-sdk/credential-provider-cognito-identity/@aws-sdk/nested-clients/@aws-sdk/core/@aws-sdk/xml-builder/fast-xml-parser/strnum": ["strnum@2.2.0", "", {}, "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache/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=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/minipass-fetch/minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
||||
|
||||
"archiver-utils/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"archiver-utils/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"cacache/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"js-beautify/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"js-beautify/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"@electron/rebuild/node-gyp/make-fetch-happen/cacache/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +122,6 @@ const ZEN_LITE_PRICE = new sst.Linkable("ZEN_LITE_PRICE", {
|
||||
properties: {
|
||||
product: zenLiteProduct.id,
|
||||
price: zenLitePrice.id,
|
||||
priceInr: 92900,
|
||||
firstMonth50Coupon: zenLiteCouponFirstMonth50.id,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
|
||||
import { expect, type Locator, type Page } from "@playwright/test"
|
||||
import fs from "node:fs/promises"
|
||||
import os from "node:os"
|
||||
@@ -362,30 +361,6 @@ export async function waitSlug(page: Page, skip: string[] = []) {
|
||||
return next
|
||||
}
|
||||
|
||||
export async function resolveSlug(slug: string) {
|
||||
const directory = base64Decode(slug)
|
||||
if (!directory) throw new Error(`Failed to decode workspace slug: ${slug}`)
|
||||
const resolved = await resolveDirectory(directory)
|
||||
return { directory: resolved, slug: base64Encode(resolved), raw: slug }
|
||||
}
|
||||
|
||||
export async function waitDir(page: Page, directory: string) {
|
||||
const target = await resolveDirectory(directory)
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const slug = slugFromUrl(page.url())
|
||||
if (!slug) return ""
|
||||
return resolveSlug(slug)
|
||||
.then((item) => item.directory)
|
||||
.catch(() => "")
|
||||
},
|
||||
{ timeout: 45_000 },
|
||||
)
|
||||
.toBe(target)
|
||||
return { directory: target, slug: base64Encode(target) }
|
||||
}
|
||||
|
||||
export function sessionIDFromUrl(url: string) {
|
||||
const match = /\/session\/([^/?#]+)/.exec(url)
|
||||
return match?.[1]
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import { base64Decode } from "@opencode-ai/util/encode"
|
||||
import type { Page } from "@playwright/test"
|
||||
import { test, expect } from "../fixtures"
|
||||
import {
|
||||
defocus,
|
||||
createTestProject,
|
||||
cleanupTestProject,
|
||||
openSidebar,
|
||||
sessionIDFromUrl,
|
||||
waitDir,
|
||||
waitSlug,
|
||||
} from "../actions"
|
||||
import { defocus, createTestProject, cleanupTestProject, openSidebar, sessionIDFromUrl, waitSlug } from "../actions"
|
||||
import { projectSwitchSelector, promptSelector, workspaceItemSelector, workspaceNewSessionSelector } from "../selectors"
|
||||
import { dirSlug, resolveDirectory } from "../utils"
|
||||
|
||||
@@ -108,8 +100,11 @@ test("switching back to a project opens the latest workspace session", async ({
|
||||
await expect(btn).toBeVisible()
|
||||
await btn.click({ force: true })
|
||||
|
||||
// A new workspace can be discovered via a transient slug before the route and sidebar
|
||||
// settle to the canonical workspace path on Windows, so interact with either and assert
|
||||
// against the resolved workspace slug.
|
||||
await waitSlug(page)
|
||||
await waitDir(page, space)
|
||||
await expect(page).toHaveURL(new RegExp(`/${next}/session(?:[/?#]|$)`))
|
||||
|
||||
// Create a session by sending a prompt
|
||||
const prompt = page.locator(promptSelector)
|
||||
@@ -137,7 +132,6 @@ test("switching back to a project opens the latest workspace session", async ({
|
||||
await expect(rootButton).toBeVisible()
|
||||
await rootButton.click()
|
||||
|
||||
await waitDir(page, space)
|
||||
await expect.poll(() => sessionIDFromUrl(page.url()) ?? "").toBe(created)
|
||||
await expect(page).toHaveURL(new RegExp(`/session/${created}(?:[/?#]|$)`))
|
||||
},
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
import { base64Decode } from "@opencode-ai/util/encode"
|
||||
import type { Page } from "@playwright/test"
|
||||
import { test, expect } from "../fixtures"
|
||||
import { openSidebar, resolveSlug, sessionIDFromUrl, setWorkspacesEnabled, waitDir, waitSlug } from "../actions"
|
||||
import { openSidebar, sessionIDFromUrl, setWorkspacesEnabled, slugFromUrl, waitSlug } from "../actions"
|
||||
import { promptSelector, workspaceItemSelector, workspaceNewSessionSelector } from "../selectors"
|
||||
import { createSdk } from "../utils"
|
||||
|
||||
function item(space: { slug: string; raw: string }) {
|
||||
return `${workspaceItemSelector(space.slug)}, ${workspaceItemSelector(space.raw)}`
|
||||
}
|
||||
|
||||
function button(space: { slug: string; raw: string }) {
|
||||
return `${workspaceNewSessionSelector(space.slug)}, ${workspaceNewSessionSelector(space.raw)}`
|
||||
}
|
||||
|
||||
async function waitWorkspaceReady(page: Page, space: { slug: string; raw: string }) {
|
||||
async function waitWorkspaceReady(page: Page, slug: string) {
|
||||
await openSidebar(page)
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const row = page.locator(item(space)).first()
|
||||
const item = page.locator(workspaceItemSelector(slug)).first()
|
||||
try {
|
||||
await row.hover({ timeout: 500 })
|
||||
await item.hover({ timeout: 500 })
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
@@ -34,30 +27,29 @@ async function createWorkspace(page: Page, root: string, seen: string[]) {
|
||||
await openSidebar(page)
|
||||
await page.getByRole("button", { name: "New workspace" }).first().click()
|
||||
|
||||
const next = await resolveSlug(await waitSlug(page, [root, ...seen]))
|
||||
await waitDir(page, next.directory)
|
||||
const slug = await waitSlug(page, [root, ...seen])
|
||||
const directory = base64Decode(slug)
|
||||
if (!directory) throw new Error(`Failed to decode workspace slug: ${slug}`)
|
||||
return { slug, directory }
|
||||
}
|
||||
|
||||
async function openWorkspaceNewSession(page: Page, slug: string) {
|
||||
await waitWorkspaceReady(page, slug)
|
||||
|
||||
const item = page.locator(workspaceItemSelector(slug)).first()
|
||||
await item.hover()
|
||||
|
||||
const button = page.locator(workspaceNewSessionSelector(slug)).first()
|
||||
await expect(button).toBeVisible()
|
||||
await button.click({ force: true })
|
||||
|
||||
const next = await waitSlug(page)
|
||||
await expect(page).toHaveURL(new RegExp(`/${next}/session(?:[/?#]|$)`))
|
||||
return next
|
||||
}
|
||||
|
||||
async function openWorkspaceNewSession(page: Page, space: { slug: string; raw: string; directory: string }) {
|
||||
await waitWorkspaceReady(page, space)
|
||||
|
||||
const row = page.locator(item(space)).first()
|
||||
await row.hover()
|
||||
|
||||
const next = page.locator(button(space)).first()
|
||||
await expect(next).toBeVisible()
|
||||
await next.click({ force: true })
|
||||
|
||||
return waitDir(page, space.directory)
|
||||
}
|
||||
|
||||
async function createSessionFromWorkspace(
|
||||
page: Page,
|
||||
space: { slug: string; raw: string; directory: string },
|
||||
text: string,
|
||||
) {
|
||||
const next = await openWorkspaceNewSession(page, space)
|
||||
async function createSessionFromWorkspace(page: Page, slug: string, text: string) {
|
||||
const next = await openWorkspaceNewSession(page, slug)
|
||||
|
||||
const prompt = page.locator(promptSelector)
|
||||
await expect(prompt).toBeVisible()
|
||||
@@ -68,13 +60,13 @@ async function createSessionFromWorkspace(
|
||||
await expect.poll(async () => ((await prompt.textContent()) ?? "").trim()).toContain(text)
|
||||
await prompt.press("Enter")
|
||||
|
||||
await waitDir(page, next.directory)
|
||||
await expect.poll(() => slugFromUrl(page.url())).toBe(next)
|
||||
await expect.poll(() => sessionIDFromUrl(page.url()) ?? "", { timeout: 30_000 }).not.toBe("")
|
||||
|
||||
const sessionID = sessionIDFromUrl(page.url())
|
||||
if (!sessionID) throw new Error(`Failed to parse session id from url: ${page.url()}`)
|
||||
await expect(page).toHaveURL(new RegExp(`/session/${sessionID}(?:[/?#]|$)`))
|
||||
return { sessionID, slug: next.slug }
|
||||
await expect(page).toHaveURL(new RegExp(`/${next}/session/${sessionID}(?:[/?#]|$)`))
|
||||
return { sessionID, slug: next }
|
||||
}
|
||||
|
||||
async function sessionDirectory(directory: string, sessionID: string) {
|
||||
@@ -95,11 +87,11 @@ test("new sessions from sidebar workspace actions stay in selected workspace", a
|
||||
|
||||
const first = await createWorkspace(page, root, [])
|
||||
trackDirectory(first.directory)
|
||||
await waitWorkspaceReady(page, first)
|
||||
await waitWorkspaceReady(page, first.slug)
|
||||
|
||||
const second = await createWorkspace(page, root, [first.slug])
|
||||
trackDirectory(second.directory)
|
||||
await waitWorkspaceReady(page, second)
|
||||
await waitWorkspaceReady(page, second.slug)
|
||||
|
||||
const firstSession = await createSessionFromWorkspace(page, first.slug, `workspace one ${Date.now()}`)
|
||||
trackSession(firstSession.sessionID, first.directory)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { base64Decode } from "@opencode-ai/util/encode"
|
||||
import fs from "node:fs/promises"
|
||||
import os from "node:os"
|
||||
import path from "node:path"
|
||||
import { base64Decode } from "@opencode-ai/util/encode"
|
||||
import type { Page } from "@playwright/test"
|
||||
|
||||
import { test, expect } from "../fixtures"
|
||||
@@ -13,10 +13,8 @@ import {
|
||||
confirmDialog,
|
||||
openSidebar,
|
||||
openWorkspaceMenu,
|
||||
resolveSlug,
|
||||
setWorkspacesEnabled,
|
||||
slugFromUrl,
|
||||
waitDir,
|
||||
waitSlug,
|
||||
} from "../actions"
|
||||
import { dropdownMenuContentSelector, inlineInputSelector, workspaceItemSelector } from "../selectors"
|
||||
@@ -29,15 +27,15 @@ async function setupWorkspaceTest(page: Page, project: { slug: string }) {
|
||||
await setWorkspacesEnabled(page, rootSlug, true)
|
||||
|
||||
await page.getByRole("button", { name: "New workspace" }).first().click()
|
||||
const next = await resolveSlug(await waitSlug(page, [rootSlug]))
|
||||
await waitDir(page, next.directory)
|
||||
const slug = await waitSlug(page, [rootSlug])
|
||||
const dir = base64Decode(slug)
|
||||
|
||||
await openSidebar(page)
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const item = page.locator(workspaceItemSelector(next.slug)).first()
|
||||
const item = page.locator(workspaceItemSelector(slug)).first()
|
||||
try {
|
||||
await item.hover({ timeout: 500 })
|
||||
return true
|
||||
@@ -49,7 +47,7 @@ async function setupWorkspaceTest(page: Page, project: { slug: string }) {
|
||||
)
|
||||
.toBe(true)
|
||||
|
||||
return { rootSlug, slug: next.slug, directory: next.directory }
|
||||
return { rootSlug, slug, directory: dir }
|
||||
}
|
||||
|
||||
test("can enable and disable workspaces from project menu", async ({ page, withProject }) => {
|
||||
@@ -81,15 +79,15 @@ test("can create a workspace", async ({ page, withProject }) => {
|
||||
await expect(page.getByRole("button", { name: "New workspace" }).first()).toBeVisible()
|
||||
|
||||
await page.getByRole("button", { name: "New workspace" }).first().click()
|
||||
const next = await resolveSlug(await waitSlug(page, [slug]))
|
||||
await waitDir(page, next.directory)
|
||||
const workspaceSlug = await waitSlug(page, [slug])
|
||||
const workspaceDir = base64Decode(workspaceSlug)
|
||||
|
||||
await openSidebar(page)
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const item = page.locator(workspaceItemSelector(next.slug)).first()
|
||||
const item = page.locator(workspaceItemSelector(workspaceSlug)).first()
|
||||
try {
|
||||
await item.hover({ timeout: 500 })
|
||||
return true
|
||||
@@ -101,9 +99,9 @@ test("can create a workspace", async ({ page, withProject }) => {
|
||||
)
|
||||
.toBe(true)
|
||||
|
||||
await expect(page.locator(workspaceItemSelector(next.slug)).first()).toBeVisible()
|
||||
await expect(page.locator(workspaceItemSelector(workspaceSlug)).first()).toBeVisible()
|
||||
|
||||
await cleanupTestProject(next.directory)
|
||||
await cleanupTestProject(workspaceDir)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -121,7 +119,7 @@ test("non-git projects keep workspace mode disabled", async ({ page, withProject
|
||||
|
||||
await expect.poll(() => slugFromUrl(page.url()), { timeout: 30_000 }).not.toBe("")
|
||||
|
||||
const activeDir = await resolveSlug(slugFromUrl(page.url())).then((item) => item.directory)
|
||||
const activeDir = base64Decode(slugFromUrl(page.url()))
|
||||
expect(path.basename(activeDir)).toContain("opencode-e2e-project-nongit-")
|
||||
|
||||
await openSidebar(page)
|
||||
@@ -333,9 +331,9 @@ test("can reorder workspaces by drag and drop", async ({ page, withProject }) =>
|
||||
for (const _ of [0, 1]) {
|
||||
const prev = slugFromUrl(page.url())
|
||||
await page.getByRole("button", { name: "New workspace" }).first().click()
|
||||
const next = await resolveSlug(await waitSlug(page, [rootSlug, prev]))
|
||||
await waitDir(page, next.directory)
|
||||
workspaces.push(next)
|
||||
const slug = await waitSlug(page, [rootSlug, prev])
|
||||
const dir = base64Decode(slug)
|
||||
workspaces.push({ slug, directory: dir })
|
||||
|
||||
await openSidebar(page)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { base64Decode } from "@opencode-ai/util/encode"
|
||||
import type { Locator, Page } from "@playwright/test"
|
||||
import { test, expect } from "../fixtures"
|
||||
import { openSidebar, resolveSlug, sessionIDFromUrl, setWorkspacesEnabled, waitSessionIdle, waitSlug } from "../actions"
|
||||
import { openSidebar, sessionIDFromUrl, setWorkspacesEnabled, waitSessionIdle, waitSlug } from "../actions"
|
||||
import {
|
||||
promptAgentSelector,
|
||||
promptModelSelector,
|
||||
@@ -223,9 +224,10 @@ async function createWorkspace(page: Page, root: string, seen: string[]) {
|
||||
await openSidebar(page)
|
||||
await page.getByRole("button", { name: "New workspace" }).first().click()
|
||||
|
||||
const next = await resolveSlug(await waitSlug(page, [root, ...seen]))
|
||||
await expect(page).toHaveURL(new RegExp(`/${next.slug}/session(?:[/?#]|$)`))
|
||||
return next
|
||||
const slug = await waitSlug(page, [root, ...seen])
|
||||
const directory = base64Decode(slug)
|
||||
if (!directory) throw new Error(`Failed to decode workspace slug: ${slug}`)
|
||||
return { slug, directory }
|
||||
}
|
||||
|
||||
async function waitWorkspace(page: Page, slug: string) {
|
||||
@@ -255,8 +257,8 @@ async function newWorkspaceSession(page: Page, slug: string) {
|
||||
await expect(button).toBeVisible()
|
||||
await button.click({ force: true })
|
||||
|
||||
const next = await resolveSlug(await waitSlug(page))
|
||||
await expect(page).toHaveURL(new RegExp(`/${next.slug}/session(?:[/?#]|$)`))
|
||||
const next = await waitSlug(page)
|
||||
await expect(page).toHaveURL(new RegExp(`/${next}/session(?:[/?#]|$)`))
|
||||
await expect(page.locator(promptSelector)).toBeVisible()
|
||||
return currentDir(page)
|
||||
}
|
||||
|
||||
@@ -46,13 +46,21 @@ import Layout from "@/pages/layout"
|
||||
import { ErrorPage } from "./pages/error"
|
||||
import { useCheckServerHealth } from "./utils/server-health"
|
||||
|
||||
const HomeRoute = lazy(() => import("@/pages/home"))
|
||||
const Home = lazy(() => import("@/pages/home"))
|
||||
const Session = lazy(() => import("@/pages/session"))
|
||||
const Loading = () => <div class="size-full" />
|
||||
|
||||
const HomeRoute = () => (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Home />
|
||||
</Suspense>
|
||||
)
|
||||
|
||||
const SessionRoute = () => (
|
||||
<SessionProviders>
|
||||
<Session />
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Session />
|
||||
</Suspense>
|
||||
</SessionProviders>
|
||||
)
|
||||
|
||||
@@ -116,10 +124,8 @@ function SessionProviders(props: ParentProps) {
|
||||
function RouterRoot(props: ParentProps<{ appChildren?: JSX.Element }>) {
|
||||
return (
|
||||
<AppShellProviders>
|
||||
<Suspense fallback={<Loading />}>
|
||||
{props.appChildren}
|
||||
{props.children}
|
||||
</Suspense>
|
||||
{props.appChildren}
|
||||
{props.children}
|
||||
</AppShellProviders>
|
||||
)
|
||||
}
|
||||
@@ -259,15 +265,6 @@ function ConnectionError(props: { onRetry?: () => void; onServerSelected?: (key:
|
||||
)
|
||||
}
|
||||
|
||||
function ServerKey(props: ParentProps) {
|
||||
const server = useServer()
|
||||
return (
|
||||
<Show when={server.key} keyed>
|
||||
{props.children}
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
export function AppInterface(props: {
|
||||
children?: JSX.Element
|
||||
defaultServer: ServerConnection.Key
|
||||
@@ -278,22 +275,20 @@ export function AppInterface(props: {
|
||||
return (
|
||||
<ServerProvider defaultServer={props.defaultServer} servers={props.servers}>
|
||||
<ConnectionGate disableHealthCheck={props.disableHealthCheck}>
|
||||
<ServerKey>
|
||||
<GlobalSDKProvider>
|
||||
<GlobalSyncProvider>
|
||||
<Dynamic
|
||||
component={props.router ?? Router}
|
||||
root={(routerProps) => <RouterRoot appChildren={props.children}>{routerProps.children}</RouterRoot>}
|
||||
>
|
||||
<Route path="/" component={HomeRoute} />
|
||||
<Route path="/:dir" component={DirectoryLayout}>
|
||||
<Route path="/" component={SessionIndexRoute} />
|
||||
<Route path="/session/:id?" component={SessionRoute} />
|
||||
</Route>
|
||||
</Dynamic>
|
||||
</GlobalSyncProvider>
|
||||
</GlobalSDKProvider>
|
||||
</ServerKey>
|
||||
<GlobalSDKProvider>
|
||||
<GlobalSyncProvider>
|
||||
<Dynamic
|
||||
component={props.router ?? Router}
|
||||
root={(routerProps) => <RouterRoot appChildren={props.children}>{routerProps.children}</RouterRoot>}
|
||||
>
|
||||
<Route path="/" component={HomeRoute} />
|
||||
<Route path="/:dir" component={DirectoryLayout}>
|
||||
<Route path="/" component={SessionIndexRoute} />
|
||||
<Route path="/session/:id?" component={SessionRoute} />
|
||||
</Route>
|
||||
</Dynamic>
|
||||
</GlobalSyncProvider>
|
||||
</GlobalSDKProvider>
|
||||
</ConnectionGate>
|
||||
</ServerProvider>
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Link } from "@/components/link"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { useGlobalSDK } from "@/context/global-sdk"
|
||||
import { useGlobalSync } from "@/context/global-sync"
|
||||
import { usePlatform } from "@/context/platform"
|
||||
import { DialogSelectModel } from "./dialog-select-model"
|
||||
import { DialogSelectProvider } from "./dialog-select-provider"
|
||||
|
||||
@@ -22,6 +23,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
const dialog = useDialog()
|
||||
const globalSync = useGlobalSync()
|
||||
const globalSDK = useGlobalSDK()
|
||||
const platform = usePlatform()
|
||||
const language = useLanguage()
|
||||
|
||||
const alive = { value: true }
|
||||
@@ -47,14 +49,13 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
const [store, setStore] = createStore({
|
||||
methodIndex: undefined as undefined | number,
|
||||
authorization: undefined as undefined | ProviderAuthAuthorization,
|
||||
state: "pending" as undefined | "pending" | "complete" | "error" | "prompt",
|
||||
state: "pending" as undefined | "pending" | "complete" | "error",
|
||||
error: undefined as string | undefined,
|
||||
})
|
||||
|
||||
type Action =
|
||||
| { type: "method.select"; index: number }
|
||||
| { type: "method.reset" }
|
||||
| { type: "auth.prompt" }
|
||||
| { type: "auth.pending" }
|
||||
| { type: "auth.complete"; authorization: ProviderAuthAuthorization }
|
||||
| { type: "auth.error"; error: string }
|
||||
@@ -76,11 +77,6 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
draft.error = undefined
|
||||
return
|
||||
}
|
||||
if (action.type === "auth.prompt") {
|
||||
draft.state = "prompt"
|
||||
draft.error = undefined
|
||||
return
|
||||
}
|
||||
if (action.type === "auth.pending") {
|
||||
draft.state = "pending"
|
||||
draft.error = undefined
|
||||
@@ -124,7 +120,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
return fallback
|
||||
}
|
||||
|
||||
async function selectMethod(index: number, inputs?: Record<string, string>) {
|
||||
async function selectMethod(index: number) {
|
||||
if (timer.current !== undefined) {
|
||||
clearTimeout(timer.current)
|
||||
timer.current = undefined
|
||||
@@ -134,10 +130,6 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
dispatch({ type: "method.select", index })
|
||||
|
||||
if (method.type === "oauth") {
|
||||
if (method.prompts?.length && !inputs) {
|
||||
dispatch({ type: "auth.prompt" })
|
||||
return
|
||||
}
|
||||
dispatch({ type: "auth.pending" })
|
||||
const start = Date.now()
|
||||
await globalSDK.client.provider.oauth
|
||||
@@ -145,7 +137,6 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
{
|
||||
providerID: props.provider,
|
||||
method: index,
|
||||
inputs,
|
||||
},
|
||||
{ throwOnError: true },
|
||||
)
|
||||
@@ -172,122 +163,6 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
}
|
||||
}
|
||||
|
||||
function OAuthPromptsView() {
|
||||
const [formStore, setFormStore] = createStore({
|
||||
value: {} as Record<string, string>,
|
||||
index: 0,
|
||||
})
|
||||
|
||||
const prompts = createMemo(() => method()?.prompts ?? [])
|
||||
const matches = (prompt: NonNullable<ReturnType<typeof prompts>[number]>, value: Record<string, string>) => {
|
||||
if (!prompt.when) return true
|
||||
const actual = value[prompt.when.key]
|
||||
if (actual === undefined) return false
|
||||
return prompt.when.op === "eq" ? actual === prompt.when.value : actual !== prompt.when.value
|
||||
}
|
||||
const current = createMemo(() => {
|
||||
const all = prompts()
|
||||
const index = all.findIndex((prompt, index) => index >= formStore.index && matches(prompt, formStore.value))
|
||||
if (index === -1) return
|
||||
return {
|
||||
index,
|
||||
prompt: all[index],
|
||||
}
|
||||
})
|
||||
const valid = createMemo(() => {
|
||||
const item = current()
|
||||
if (!item || item.prompt.type !== "text") return false
|
||||
const value = formStore.value[item.prompt.key] ?? ""
|
||||
return value.trim().length > 0
|
||||
})
|
||||
|
||||
async function next(index: number, value: Record<string, string>) {
|
||||
if (store.methodIndex === undefined) return
|
||||
const next = prompts().findIndex((prompt, i) => i > index && matches(prompt, value))
|
||||
if (next !== -1) {
|
||||
setFormStore("index", next)
|
||||
return
|
||||
}
|
||||
await selectMethod(store.methodIndex, value)
|
||||
}
|
||||
|
||||
async function handleSubmit(e: SubmitEvent) {
|
||||
e.preventDefault()
|
||||
const item = current()
|
||||
if (!item || item.prompt.type !== "text") return
|
||||
if (!valid()) return
|
||||
await next(item.index, formStore.value)
|
||||
}
|
||||
|
||||
const item = () => current()
|
||||
const text = createMemo(() => {
|
||||
const prompt = item()?.prompt
|
||||
if (!prompt || prompt.type !== "text") return
|
||||
return prompt
|
||||
})
|
||||
const select = createMemo(() => {
|
||||
const prompt = item()?.prompt
|
||||
if (!prompt || prompt.type !== "select") return
|
||||
return prompt
|
||||
})
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
|
||||
<Switch>
|
||||
<Match when={item()?.prompt.type === "text"}>
|
||||
<TextField
|
||||
type="text"
|
||||
label={text()?.message ?? ""}
|
||||
placeholder={text()?.placeholder}
|
||||
value={text() ? (formStore.value[text()!.key] ?? "") : ""}
|
||||
onChange={(value) => {
|
||||
const prompt = text()
|
||||
if (!prompt) return
|
||||
setFormStore("value", prompt.key, value)
|
||||
}}
|
||||
/>
|
||||
<Button class="w-auto" type="submit" size="large" variant="primary" disabled={!valid()}>
|
||||
{language.t("common.continue")}
|
||||
</Button>
|
||||
</Match>
|
||||
<Match when={item()?.prompt.type === "select"}>
|
||||
<div class="w-full flex flex-col gap-1.5">
|
||||
<div class="text-14-regular text-text-base">{select()?.message}</div>
|
||||
<div>
|
||||
<List
|
||||
items={select()?.options ?? []}
|
||||
key={(x) => x.value}
|
||||
current={select()?.options.find((x) => x.value === formStore.value[select()!.key])}
|
||||
onSelect={(value) => {
|
||||
if (!value) return
|
||||
const prompt = select()
|
||||
if (!prompt) return
|
||||
const nextValue = {
|
||||
...formStore.value,
|
||||
[prompt.key]: value.value,
|
||||
}
|
||||
setFormStore("value", prompt.key, value.value)
|
||||
void next(item()!.index, nextValue)
|
||||
}}
|
||||
>
|
||||
{(option) => (
|
||||
<div class="w-full flex items-center gap-x-2">
|
||||
<div class="w-4 h-2 rounded-[1px] bg-input-base shadow-xs-border-base flex items-center justify-center">
|
||||
<div class="w-2.5 h-0.5 ml-0 bg-icon-strong-base hidden" data-slot="list-item-extra-icon" />
|
||||
</div>
|
||||
<span>{option.label}</span>
|
||||
<span class="text-14-regular text-text-weak">{option.hint}</span>
|
||||
</div>
|
||||
)}
|
||||
</List>
|
||||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
</Switch>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
let listRef: ListRef | undefined
|
||||
function handleKey(e: KeyboardEvent) {
|
||||
if (e.key === "Enter" && e.target instanceof HTMLInputElement) {
|
||||
@@ -426,7 +301,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
error={formStore.error}
|
||||
/>
|
||||
<Button class="w-auto" type="submit" size="large" variant="primary">
|
||||
{language.t("common.continue")}
|
||||
{language.t("common.submit")}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -439,6 +314,12 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
error: undefined as string | undefined,
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
if (store.authorization?.method === "code" && store.authorization?.url) {
|
||||
platform.openLink(store.authorization.url)
|
||||
}
|
||||
})
|
||||
|
||||
async function handleSubmit(e: SubmitEvent) {
|
||||
e.preventDefault()
|
||||
|
||||
@@ -487,7 +368,7 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
error={formStore.error}
|
||||
/>
|
||||
<Button class="w-auto" type="submit" size="large" variant="primary">
|
||||
{language.t("common.continue")}
|
||||
{language.t("common.submit")}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -505,6 +386,10 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
|
||||
onMount(() => {
|
||||
void (async () => {
|
||||
if (store.authorization?.url) {
|
||||
platform.openLink(store.authorization.url)
|
||||
}
|
||||
|
||||
const result = await globalSDK.client.provider.oauth
|
||||
.callback({
|
||||
providerID: props.provider,
|
||||
@@ -585,9 +470,6 @@ export function DialogConnectProvider(props: { provider: string }) {
|
||||
</div>
|
||||
</div>
|
||||
</Match>
|
||||
<Match when={store.state === "prompt"}>
|
||||
<OAuthPromptsView />
|
||||
</Match>
|
||||
<Match when={store.state === "error"}>
|
||||
<div class="text-14-regular text-text-base">
|
||||
<div class="flex items-center gap-x-2">
|
||||
|
||||
@@ -291,8 +291,8 @@ export function DialogSelectServer() {
|
||||
navigate("/")
|
||||
return
|
||||
}
|
||||
server.setActive(ServerConnection.key(conn))
|
||||
navigate("/")
|
||||
queueMicrotask(() => server.setActive(ServerConnection.key(conn)))
|
||||
}
|
||||
|
||||
const handleAddChange = (value: string) => {
|
||||
|
||||
@@ -1241,20 +1241,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
// Note: Shift+Enter is handled earlier, before IME check
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault()
|
||||
if (event.repeat) return
|
||||
if (
|
||||
working() &&
|
||||
prompt
|
||||
.current()
|
||||
.map((part) => ("content" in part ? part.content : ""))
|
||||
.join("")
|
||||
.trim().length === 0 &&
|
||||
imageAttachments().length === 0 &&
|
||||
commentCount() === 0
|
||||
) {
|
||||
return
|
||||
}
|
||||
handleSubmit(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,8 +277,8 @@ export function StatusPopover() {
|
||||
aria-disabled={isBlocked()}
|
||||
onClick={() => {
|
||||
if (isBlocked()) return
|
||||
server.setActive(key)
|
||||
navigate("/")
|
||||
queueMicrotask(() => server.setActive(key))
|
||||
}}
|
||||
>
|
||||
<ServerHealthIndicator health={health[key]} />
|
||||
|
||||
@@ -165,12 +165,6 @@ export const Terminal = (props: TerminalProps) => {
|
||||
const theme = useTheme()
|
||||
const language = useLanguage()
|
||||
const server = useServer()
|
||||
const directory = sdk.directory
|
||||
const client = sdk.client
|
||||
const url = sdk.url
|
||||
const auth = server.current?.http
|
||||
const username = auth?.username ?? "opencode"
|
||||
const password = auth?.password ?? ""
|
||||
let container!: HTMLDivElement
|
||||
const [local, others] = splitProps(props, ["pty", "class", "classList", "autoFocus", "onConnect", "onConnectError"])
|
||||
const id = local.pty.id
|
||||
@@ -221,7 +215,7 @@ export const Terminal = (props: TerminalProps) => {
|
||||
}
|
||||
|
||||
const pushSize = (cols: number, rows: number) => {
|
||||
return client.pty
|
||||
return sdk.client.pty
|
||||
.update({
|
||||
ptyID: id,
|
||||
size: { cols, rows },
|
||||
@@ -480,7 +474,7 @@ export const Terminal = (props: TerminalProps) => {
|
||||
}
|
||||
|
||||
const gone = () =>
|
||||
client.pty
|
||||
sdk.client.pty
|
||||
.get({ ptyID: id })
|
||||
.then(() => false)
|
||||
.catch((err) => {
|
||||
@@ -512,14 +506,14 @@ export const Terminal = (props: TerminalProps) => {
|
||||
if (disposed) return
|
||||
drop?.()
|
||||
|
||||
const next = new URL(url + `/pty/${id}/connect`)
|
||||
next.searchParams.set("directory", directory)
|
||||
next.searchParams.set("cursor", String(seek))
|
||||
next.protocol = next.protocol === "https:" ? "wss:" : "ws:"
|
||||
next.username = username
|
||||
next.password = password
|
||||
const url = new URL(sdk.url + `/pty/${id}/connect`)
|
||||
url.searchParams.set("directory", sdk.directory)
|
||||
url.searchParams.set("cursor", String(seek))
|
||||
url.protocol = url.protocol === "https:" ? "wss:" : "ws:"
|
||||
url.username = server.current?.http.username ?? "opencode"
|
||||
url.password = server.current?.http.password ?? ""
|
||||
|
||||
const socket = new WebSocket(next)
|
||||
const socket = new WebSocket(url)
|
||||
socket.binaryType = "arraybuffer"
|
||||
ws = socket
|
||||
|
||||
|
||||
@@ -185,60 +185,6 @@ function createWorkspaceTerminalSession(sdk: ReturnType<typeof useSDK>, dir: str
|
||||
})
|
||||
onCleanup(unsub)
|
||||
|
||||
const update = (client: ReturnType<typeof useSDK>["client"], pty: Partial<LocalPTY> & { id: string }) => {
|
||||
const index = store.all.findIndex((x) => x.id === pty.id)
|
||||
const previous = index >= 0 ? store.all[index] : undefined
|
||||
if (index >= 0) {
|
||||
setStore("all", index, (item) => ({ ...item, ...pty }))
|
||||
}
|
||||
client.pty
|
||||
.update({
|
||||
ptyID: pty.id,
|
||||
title: pty.title,
|
||||
size: pty.cols && pty.rows ? { rows: pty.rows, cols: pty.cols } : undefined,
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
if (previous) {
|
||||
const currentIndex = store.all.findIndex((item) => item.id === pty.id)
|
||||
if (currentIndex >= 0) setStore("all", currentIndex, previous)
|
||||
}
|
||||
console.error("Failed to update terminal", error)
|
||||
})
|
||||
}
|
||||
|
||||
const clone = async (client: ReturnType<typeof useSDK>["client"], id: string) => {
|
||||
const index = store.all.findIndex((x) => x.id === id)
|
||||
const pty = store.all[index]
|
||||
if (!pty) return
|
||||
const next = await client.pty
|
||||
.create({
|
||||
title: pty.title,
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
console.error("Failed to clone terminal", error)
|
||||
return undefined
|
||||
})
|
||||
if (!next?.data) return
|
||||
|
||||
const active = store.active === pty.id
|
||||
|
||||
batch(() => {
|
||||
setStore("all", index, {
|
||||
id: next.data.id,
|
||||
title: next.data.title ?? pty.title,
|
||||
titleNumber: pty.titleNumber,
|
||||
buffer: undefined,
|
||||
cursor: undefined,
|
||||
scrollY: undefined,
|
||||
rows: undefined,
|
||||
cols: undefined,
|
||||
})
|
||||
if (active) {
|
||||
setStore("active", next.data.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
ready,
|
||||
all: createMemo(() => store.all),
|
||||
@@ -270,7 +216,24 @@ function createWorkspaceTerminalSession(sdk: ReturnType<typeof useSDK>, dir: str
|
||||
})
|
||||
},
|
||||
update(pty: Partial<LocalPTY> & { id: string }) {
|
||||
update(sdk.client, pty)
|
||||
const index = store.all.findIndex((x) => x.id === pty.id)
|
||||
const previous = index >= 0 ? store.all[index] : undefined
|
||||
if (index >= 0) {
|
||||
setStore("all", index, (item) => ({ ...item, ...pty }))
|
||||
}
|
||||
sdk.client.pty
|
||||
.update({
|
||||
ptyID: pty.id,
|
||||
title: pty.title,
|
||||
size: pty.cols && pty.rows ? { rows: pty.rows, cols: pty.cols } : undefined,
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
if (previous) {
|
||||
const currentIndex = store.all.findIndex((item) => item.id === pty.id)
|
||||
if (currentIndex >= 0) setStore("all", currentIndex, previous)
|
||||
}
|
||||
console.error("Failed to update terminal", error)
|
||||
})
|
||||
},
|
||||
trim(id: string) {
|
||||
const index = store.all.findIndex((x) => x.id === id)
|
||||
@@ -285,23 +248,37 @@ function createWorkspaceTerminalSession(sdk: ReturnType<typeof useSDK>, dir: str
|
||||
})
|
||||
},
|
||||
async clone(id: string) {
|
||||
await clone(sdk.client, id)
|
||||
},
|
||||
bind() {
|
||||
const client = sdk.client
|
||||
return {
|
||||
trim(id: string) {
|
||||
const index = store.all.findIndex((x) => x.id === id)
|
||||
if (index === -1) return
|
||||
setStore("all", index, (pty) => trimTerminal(pty))
|
||||
},
|
||||
update(pty: Partial<LocalPTY> & { id: string }) {
|
||||
update(client, pty)
|
||||
},
|
||||
async clone(id: string) {
|
||||
await clone(client, id)
|
||||
},
|
||||
}
|
||||
const index = store.all.findIndex((x) => x.id === id)
|
||||
const pty = store.all[index]
|
||||
if (!pty) return
|
||||
const clone = await sdk.client.pty
|
||||
.create({
|
||||
title: pty.title,
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
console.error("Failed to clone terminal", error)
|
||||
return undefined
|
||||
})
|
||||
if (!clone?.data) return
|
||||
|
||||
const active = store.active === pty.id
|
||||
|
||||
batch(() => {
|
||||
setStore("all", index, {
|
||||
id: clone.data.id,
|
||||
title: clone.data.title ?? pty.title,
|
||||
titleNumber: pty.titleNumber,
|
||||
// New PTY process, so start clean.
|
||||
buffer: undefined,
|
||||
cursor: undefined,
|
||||
scrollY: undefined,
|
||||
rows: undefined,
|
||||
cols: undefined,
|
||||
})
|
||||
if (active) {
|
||||
setStore("active", clone.data.id)
|
||||
}
|
||||
})
|
||||
},
|
||||
open(id: string) {
|
||||
setStore("active", id)
|
||||
@@ -426,7 +403,6 @@ export const { use: useTerminal, provider: TerminalProvider } = createSimpleCont
|
||||
trim: (id: string) => workspace().trim(id),
|
||||
trimAll: () => workspace().trimAll(),
|
||||
clone: (id: string) => workspace().clone(id),
|
||||
bind: () => workspace(),
|
||||
open: (id: string) => workspace().open(id),
|
||||
close: (id: string) => workspace().close(id),
|
||||
move: (id: string, to: number) => workspace().move(id, to),
|
||||
|
||||
@@ -204,7 +204,6 @@ export const dict = {
|
||||
"common.cancel": "إلغاء",
|
||||
"common.connect": "اتصال",
|
||||
"common.disconnect": "قطع الاتصال",
|
||||
"common.continue": "إرسال",
|
||||
"common.submit": "إرسال",
|
||||
"common.save": "حفظ",
|
||||
"common.saving": "جارٍ الحفظ...",
|
||||
|
||||
@@ -204,7 +204,6 @@ export const dict = {
|
||||
"common.cancel": "Cancelar",
|
||||
"common.connect": "Conectar",
|
||||
"common.disconnect": "Desconectar",
|
||||
"common.continue": "Enviar",
|
||||
"common.submit": "Enviar",
|
||||
"common.save": "Salvar",
|
||||
"common.saving": "Salvando...",
|
||||
|
||||
@@ -221,7 +221,6 @@ export const dict = {
|
||||
"common.cancel": "Otkaži",
|
||||
"common.connect": "Poveži",
|
||||
"common.disconnect": "Prekini vezu",
|
||||
"common.continue": "Pošalji",
|
||||
"common.submit": "Pošalji",
|
||||
"common.save": "Sačuvaj",
|
||||
"common.saving": "Čuvanje...",
|
||||
|
||||
@@ -219,7 +219,6 @@ export const dict = {
|
||||
"common.cancel": "Annuller",
|
||||
"common.connect": "Forbind",
|
||||
"common.disconnect": "Frakobl",
|
||||
"common.continue": "Indsend",
|
||||
"common.submit": "Indsend",
|
||||
"common.save": "Gem",
|
||||
"common.saving": "Gemmer...",
|
||||
|
||||
@@ -209,7 +209,6 @@ export const dict = {
|
||||
"common.cancel": "Abbrechen",
|
||||
"common.connect": "Verbinden",
|
||||
"common.disconnect": "Trennen",
|
||||
"common.continue": "Absenden",
|
||||
"common.submit": "Absenden",
|
||||
"common.save": "Speichern",
|
||||
"common.saving": "Speichert...",
|
||||
|
||||
@@ -221,7 +221,6 @@ export const dict = {
|
||||
"common.open": "Open",
|
||||
"common.connect": "Connect",
|
||||
"common.disconnect": "Disconnect",
|
||||
"common.continue": "Continue",
|
||||
"common.submit": "Submit",
|
||||
"common.save": "Save",
|
||||
"common.saving": "Saving...",
|
||||
|
||||
@@ -220,7 +220,6 @@ export const dict = {
|
||||
"common.cancel": "Cancelar",
|
||||
"common.connect": "Conectar",
|
||||
"common.disconnect": "Desconectar",
|
||||
"common.continue": "Enviar",
|
||||
"common.submit": "Enviar",
|
||||
"common.save": "Guardar",
|
||||
"common.saving": "Guardando...",
|
||||
|
||||
@@ -204,7 +204,6 @@ export const dict = {
|
||||
"common.cancel": "Annuler",
|
||||
"common.connect": "Connecter",
|
||||
"common.disconnect": "Déconnecter",
|
||||
"common.continue": "Soumettre",
|
||||
"common.submit": "Soumettre",
|
||||
"common.save": "Enregistrer",
|
||||
"common.saving": "Enregistrement...",
|
||||
|
||||
@@ -203,7 +203,6 @@ export const dict = {
|
||||
"common.cancel": "キャンセル",
|
||||
"common.connect": "接続",
|
||||
"common.disconnect": "切断",
|
||||
"common.continue": "送信",
|
||||
"common.submit": "送信",
|
||||
"common.save": "保存",
|
||||
"common.saving": "保存中...",
|
||||
|
||||
@@ -207,7 +207,6 @@ export const dict = {
|
||||
"common.cancel": "취소",
|
||||
"common.connect": "연결",
|
||||
"common.disconnect": "연결 해제",
|
||||
"common.continue": "제출",
|
||||
"common.submit": "제출",
|
||||
"common.save": "저장",
|
||||
"common.saving": "저장 중...",
|
||||
|
||||
@@ -223,7 +223,6 @@ export const dict = {
|
||||
"common.cancel": "Avbryt",
|
||||
"common.connect": "Koble til",
|
||||
"common.disconnect": "Koble fra",
|
||||
"common.continue": "Send inn",
|
||||
"common.submit": "Send inn",
|
||||
"common.save": "Lagre",
|
||||
"common.saving": "Lagrer...",
|
||||
|
||||
@@ -205,7 +205,6 @@ export const dict = {
|
||||
"common.cancel": "Anuluj",
|
||||
"common.connect": "Połącz",
|
||||
"common.disconnect": "Rozłącz",
|
||||
"common.continue": "Prześlij",
|
||||
"common.submit": "Prześlij",
|
||||
"common.save": "Zapisz",
|
||||
"common.saving": "Zapisywanie...",
|
||||
|
||||
@@ -220,7 +220,6 @@ export const dict = {
|
||||
"common.cancel": "Отмена",
|
||||
"common.connect": "Подключить",
|
||||
"common.disconnect": "Отключить",
|
||||
"common.continue": "Отправить",
|
||||
"common.submit": "Отправить",
|
||||
"common.save": "Сохранить",
|
||||
"common.saving": "Сохранение...",
|
||||
|
||||
@@ -220,7 +220,6 @@ export const dict = {
|
||||
"common.cancel": "ยกเลิก",
|
||||
"common.connect": "เชื่อมต่อ",
|
||||
"common.disconnect": "ยกเลิกการเชื่อมต่อ",
|
||||
"common.continue": "ส่ง",
|
||||
"common.submit": "ส่ง",
|
||||
"common.save": "บันทึก",
|
||||
"common.saving": "กำลังบันทึก...",
|
||||
|
||||
@@ -225,7 +225,6 @@ export const dict = {
|
||||
"common.cancel": "İptal",
|
||||
"common.connect": "Bağlan",
|
||||
"common.disconnect": "Bağlantı Kes",
|
||||
"common.continue": "Gönder",
|
||||
"common.submit": "Gönder",
|
||||
"common.save": "Kaydet",
|
||||
"common.saving": "Kaydediliyor...",
|
||||
|
||||
@@ -242,7 +242,6 @@ export const dict = {
|
||||
"common.cancel": "取消",
|
||||
"common.connect": "连接",
|
||||
"common.disconnect": "断开连接",
|
||||
"common.continue": "提交",
|
||||
"common.submit": "提交",
|
||||
"common.save": "保存",
|
||||
"common.saving": "保存中...",
|
||||
|
||||
@@ -220,7 +220,6 @@ export const dict = {
|
||||
"common.cancel": "取消",
|
||||
"common.connect": "連線",
|
||||
"common.disconnect": "中斷連線",
|
||||
"common.continue": "提交",
|
||||
"common.submit": "提交",
|
||||
"common.save": "儲存",
|
||||
"common.saving": "儲存中...",
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { DataProvider } from "@opencode-ai/ui/context"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { base64Encode } from "@opencode-ai/util/encode"
|
||||
import { batch, createEffect, createMemo, Show, type ParentProps } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useLocation, useNavigate, useParams } from "@solidjs/router"
|
||||
import { createMemo, createResource, type ParentProps, Show } from "solid-js"
|
||||
import { useGlobalSDK } from "@/context/global-sdk"
|
||||
import { useLanguage } from "@/context/language"
|
||||
import { LocalProvider } from "@/context/local"
|
||||
import { SDKProvider } from "@/context/sdk"
|
||||
import { SyncProvider, useSync } from "@/context/sync"
|
||||
import { decode64 } from "@/utils/base64"
|
||||
import { LocalProvider } from "@/context/local"
|
||||
import { useGlobalSDK } from "@/context/global-sdk"
|
||||
|
||||
import { DataProvider } from "@opencode-ai/ui/context"
|
||||
import { base64Encode } from "@opencode-ai/util/encode"
|
||||
import { decode64 } from "@/utils/base64"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
import { useLanguage } from "@/context/language"
|
||||
function DirectoryDataProvider(props: ParentProps<{ directory: string }>) {
|
||||
const navigate = useNavigate()
|
||||
const sync = useSync()
|
||||
@@ -29,53 +30,57 @@ function DirectoryDataProvider(props: ParentProps<{ directory: string }>) {
|
||||
|
||||
export default function Layout(props: ParentProps) {
|
||||
const params = useParams()
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const language = useLanguage()
|
||||
const globalSDK = useGlobalSDK()
|
||||
const navigate = useNavigate()
|
||||
let invalid = ""
|
||||
const directory = createMemo(() => decode64(params.dir) ?? "")
|
||||
const [state, setState] = createStore({ invalid: "", resolved: "" })
|
||||
|
||||
const [resolved] = createResource(
|
||||
() => {
|
||||
if (params.dir) return [location.pathname, params.dir] as const
|
||||
},
|
||||
async ([pathname, b64Dir]) => {
|
||||
const directory = decode64(b64Dir)
|
||||
createEffect(() => {
|
||||
if (!params.dir) return
|
||||
const raw = directory()
|
||||
if (!raw) {
|
||||
if (state.invalid === params.dir) return
|
||||
setState("invalid", params.dir)
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: language.t("common.requestFailed"),
|
||||
description: language.t("directory.error.invalidUrl"),
|
||||
})
|
||||
navigate("/", { replace: true })
|
||||
return
|
||||
}
|
||||
|
||||
if (!directory) {
|
||||
if (invalid === params.dir) return
|
||||
invalid = b64Dir
|
||||
showToast({
|
||||
variant: "error",
|
||||
title: language.t("common.requestFailed"),
|
||||
description: language.t("directory.error.invalidUrl"),
|
||||
const current = params.dir
|
||||
globalSDK
|
||||
.createClient({
|
||||
directory: raw,
|
||||
throwOnError: true,
|
||||
})
|
||||
.path.get()
|
||||
.then((x) => {
|
||||
if (params.dir !== current) return
|
||||
const next = x.data?.directory ?? raw
|
||||
batch(() => {
|
||||
setState("invalid", "")
|
||||
setState("resolved", next)
|
||||
})
|
||||
navigate("/", { replace: true })
|
||||
return
|
||||
}
|
||||
|
||||
return await globalSDK
|
||||
.createClient({
|
||||
directory,
|
||||
throwOnError: true,
|
||||
if (next === raw) return
|
||||
const path = location.pathname.slice(current.length + 1)
|
||||
navigate(`/${base64Encode(next)}${path}${location.search}${location.hash}`, { replace: true })
|
||||
})
|
||||
.catch(() => {
|
||||
if (params.dir !== current) return
|
||||
batch(() => {
|
||||
setState("invalid", "")
|
||||
setState("resolved", raw)
|
||||
})
|
||||
.path.get()
|
||||
.then((x) => {
|
||||
const next = x.data?.directory ?? directory
|
||||
invalid = ""
|
||||
if (next === directory) return next
|
||||
const path = pathname.slice(b64Dir.length + 1)
|
||||
navigate(`/${base64Encode(next)}${path}${location.search}${location.hash}`, { replace: true })
|
||||
})
|
||||
.catch(() => {
|
||||
invalid = ""
|
||||
return directory
|
||||
})
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<Show when={resolved()} keyed>
|
||||
<Show when={state.resolved} keyed>
|
||||
{(resolved) => (
|
||||
<SDKProvider directory={() => resolved}>
|
||||
<SyncProvider>
|
||||
|
||||
@@ -543,14 +543,13 @@ export default function Layout(props: ParentProps) {
|
||||
const currentProject = createMemo(() => {
|
||||
const directory = currentDir()
|
||||
if (!directory) return
|
||||
const key = workspaceKey(directory)
|
||||
|
||||
const projects = layout.projects.list()
|
||||
|
||||
const sandbox = projects.find((p) => p.sandboxes?.some((item) => workspaceKey(item) === key))
|
||||
const sandbox = projects.find((p) => p.sandboxes?.includes(directory))
|
||||
if (sandbox) return sandbox
|
||||
|
||||
const direct = projects.find((p) => workspaceKey(p.worktree) === key)
|
||||
const direct = projects.find((p) => p.worktree === directory)
|
||||
if (direct) return direct
|
||||
|
||||
const [child] = globalSync.child(directory, { bootstrap: false })
|
||||
@@ -631,11 +630,7 @@ export default function Layout(props: ParentProps) {
|
||||
const projects = layout.projects.list()
|
||||
for (const [directory, expanded] of Object.entries(store.workspaceExpanded)) {
|
||||
if (!expanded) continue
|
||||
const key = workspaceKey(directory)
|
||||
const project = projects.find(
|
||||
(item) =>
|
||||
workspaceKey(item.worktree) === key || item.sandboxes?.some((sandbox) => workspaceKey(sandbox) === key),
|
||||
)
|
||||
const project = projects.find((item) => item.worktree === directory || item.sandboxes?.includes(directory))
|
||||
if (!project) continue
|
||||
if (project.vcs === "git" && layout.sidebar.workspaces(project.worktree)()) continue
|
||||
setStore("workspaceExpanded", directory, false)
|
||||
@@ -1160,17 +1155,13 @@ export default function Layout(props: ParentProps) {
|
||||
}
|
||||
|
||||
function projectRoot(directory: string) {
|
||||
const key = workspaceKey(directory)
|
||||
const project = layout.projects
|
||||
.list()
|
||||
.find(
|
||||
(item) =>
|
||||
workspaceKey(item.worktree) === key || item.sandboxes?.some((sandbox) => workspaceKey(sandbox) === key),
|
||||
)
|
||||
.find((item) => item.worktree === directory || item.sandboxes?.includes(directory))
|
||||
if (project) return project.worktree
|
||||
|
||||
const known = Object.entries(store.workspaceOrder).find(
|
||||
([root, dirs]) => workspaceKey(root) === key || dirs.some((item) => workspaceKey(item) === key),
|
||||
([root, dirs]) => root === directory || dirs.includes(directory),
|
||||
)
|
||||
if (known) return known[0]
|
||||
|
||||
@@ -1186,6 +1177,13 @@ export default function Layout(props: ParentProps) {
|
||||
return currentProject()?.worktree ?? projectRoot(directory)
|
||||
}
|
||||
|
||||
function touchProjectRoute() {
|
||||
const root = currentProject()?.worktree
|
||||
if (!root) return
|
||||
if (server.projects.last() !== root) server.projects.touch(root)
|
||||
return root
|
||||
}
|
||||
|
||||
function rememberSessionRoute(directory: string, id: string, root = activeProjectRoot(directory)) {
|
||||
setStore("lastProjectSession", root, { directory, id, at: Date.now() })
|
||||
return root
|
||||
@@ -1349,9 +1347,8 @@ export default function Layout(props: ParentProps) {
|
||||
|
||||
function closeProject(directory: string) {
|
||||
const list = layout.projects.list()
|
||||
const key = workspaceKey(directory)
|
||||
const index = list.findIndex((x) => workspaceKey(x.worktree) === key)
|
||||
const active = workspaceKey(currentProject()?.worktree ?? "") === key
|
||||
const index = list.findIndex((x) => x.worktree === directory)
|
||||
const active = currentProject()?.worktree === directory
|
||||
if (index === -1) return
|
||||
const next = list[index + 1]
|
||||
|
||||
@@ -1686,55 +1683,38 @@ export default function Layout(props: ParentProps) {
|
||||
const activeRoute = {
|
||||
session: "",
|
||||
sessionProject: "",
|
||||
directory: "",
|
||||
}
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => {
|
||||
const dir = params.dir
|
||||
const directory = dir ? decode64(dir) : undefined
|
||||
const resolved = directory ? globalSync.child(directory, { bootstrap: false })[0].path.directory : ""
|
||||
return [pageReady(), dir, params.id, currentProject()?.worktree, directory, resolved] as const
|
||||
},
|
||||
([ready, dir, id, root, directory, resolved]) => {
|
||||
if (!ready || !dir || !directory) {
|
||||
() => [pageReady(), params.dir, params.id, currentProject()?.worktree] as const,
|
||||
([ready, dir, id]) => {
|
||||
if (!ready || !dir) {
|
||||
activeRoute.session = ""
|
||||
activeRoute.sessionProject = ""
|
||||
activeRoute.directory = ""
|
||||
return
|
||||
}
|
||||
|
||||
const directory = decode64(dir)
|
||||
if (!directory) return
|
||||
|
||||
const root = touchProjectRoute() ?? activeProjectRoot(directory)
|
||||
|
||||
if (!id) {
|
||||
activeRoute.session = ""
|
||||
activeRoute.sessionProject = ""
|
||||
activeRoute.directory = ""
|
||||
return
|
||||
}
|
||||
|
||||
const next = resolved || directory
|
||||
const session = `${dir}/${id}`
|
||||
|
||||
if (!root) {
|
||||
if (session !== activeRoute.session) {
|
||||
activeRoute.session = session
|
||||
activeRoute.directory = next
|
||||
activeRoute.sessionProject = ""
|
||||
return
|
||||
}
|
||||
|
||||
if (server.projects.last() !== root) server.projects.touch(root)
|
||||
|
||||
const changed = session !== activeRoute.session || next !== activeRoute.directory
|
||||
if (changed) {
|
||||
activeRoute.session = session
|
||||
activeRoute.directory = next
|
||||
activeRoute.sessionProject = syncSessionRoute(next, id, root)
|
||||
activeRoute.sessionProject = syncSessionRoute(directory, id, root)
|
||||
return
|
||||
}
|
||||
|
||||
if (root === activeRoute.sessionProject) return
|
||||
activeRoute.directory = next
|
||||
activeRoute.sessionProject = rememberSessionRoute(next, id, root)
|
||||
activeRoute.sessionProject = rememberSessionRoute(directory, id, root)
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -1798,13 +1778,8 @@ export default function Layout(props: ParentProps) {
|
||||
const local = project.worktree
|
||||
const dirs = [local, ...(project.sandboxes ?? [])]
|
||||
const active = currentProject()
|
||||
const directory = workspaceKey(active?.worktree ?? "") === workspaceKey(project.worktree) ? currentDir() : undefined
|
||||
const extra =
|
||||
directory &&
|
||||
workspaceKey(directory) !== workspaceKey(local) &&
|
||||
!dirs.some((item) => workspaceKey(item) === workspaceKey(directory))
|
||||
? directory
|
||||
: undefined
|
||||
const directory = active?.worktree === project.worktree ? currentDir() : undefined
|
||||
const extra = directory && directory !== local && !dirs.includes(directory) ? directory : undefined
|
||||
const pending = extra ? WorktreeState.get(extra)?.status === "pending" : false
|
||||
|
||||
const ordered = effectiveWorkspaceOrder(local, dirs, store.workspaceOrder[project.worktree])
|
||||
|
||||
@@ -104,14 +104,14 @@ describe("layout deep links", () => {
|
||||
describe("layout workspace helpers", () => {
|
||||
test("normalizes trailing slash in workspace key", () => {
|
||||
expect(workspaceKey("/tmp/demo///")).toBe("/tmp/demo")
|
||||
expect(workspaceKey("C:\\tmp\\demo\\\\")).toBe("C:/tmp/demo")
|
||||
expect(workspaceKey("C:\\tmp\\demo\\\\")).toBe("C:\\tmp\\demo")
|
||||
})
|
||||
|
||||
test("preserves posix and drive roots in workspace key", () => {
|
||||
expect(workspaceKey("/")).toBe("/")
|
||||
expect(workspaceKey("///")).toBe("/")
|
||||
expect(workspaceKey("C:\\")).toBe("C:/")
|
||||
expect(workspaceKey("C://")).toBe("C:/")
|
||||
expect(workspaceKey("C:\\")).toBe("C:\\")
|
||||
expect(workspaceKey("C:\\\\\\")).toBe("C:\\")
|
||||
expect(workspaceKey("C:///")).toBe("C:/")
|
||||
})
|
||||
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import { getFilename } from "@opencode-ai/util/path"
|
||||
import { type Session } from "@opencode-ai/sdk/v2/client"
|
||||
|
||||
type SessionStore = {
|
||||
session?: Session[]
|
||||
path: { directory: string }
|
||||
}
|
||||
|
||||
export const workspaceKey = (directory: string) => {
|
||||
const value = directory.replaceAll("\\", "/")
|
||||
const drive = value.match(/^([A-Za-z]:)\/+$/)
|
||||
if (drive) return `${drive[1]}/`
|
||||
if (/^\/+$/i.test(value)) return "/"
|
||||
return value.replace(/\/+$/, "")
|
||||
const drive = directory.match(/^([A-Za-z]:)[\\/]+$/)
|
||||
if (drive) return `${drive[1]}${directory.includes("\\") ? "\\" : "/"}`
|
||||
if (/^[\\/]+$/.test(directory)) return directory.includes("\\") ? "\\" : "/"
|
||||
return directory.replace(/[\\/]+$/, "")
|
||||
}
|
||||
|
||||
function sortSessions(now: number) {
|
||||
@@ -31,13 +25,13 @@ function sortSessions(now: number) {
|
||||
const isRootVisibleSession = (session: Session, directory: string) =>
|
||||
workspaceKey(session.directory) === workspaceKey(directory) && !session.parentID && !session.time?.archived
|
||||
|
||||
const roots = (store: SessionStore) =>
|
||||
(store.session ?? []).filter((session) => isRootVisibleSession(session, store.path.directory))
|
||||
export const sortedRootSessions = (store: { session: Session[]; path: { directory: string } }, now: number) =>
|
||||
store.session.filter((session) => isRootVisibleSession(session, store.path.directory)).sort(sortSessions(now))
|
||||
|
||||
export const sortedRootSessions = (store: SessionStore, now: number) => roots(store).sort(sortSessions(now))
|
||||
|
||||
export const latestRootSession = (stores: SessionStore[], now: number) =>
|
||||
stores.flatMap(roots).sort(sortSessions(now))[0]
|
||||
export const latestRootSession = (stores: { session: Session[]; path: { directory: string } }[], now: number) =>
|
||||
stores
|
||||
.flatMap((store) => store.session.filter((session) => isRootVisibleSession(session, store.path.directory)))
|
||||
.sort(sortSessions(now))[0]
|
||||
|
||||
export function hasProjectPermissions<T>(
|
||||
request: Record<string, T[] | undefined>,
|
||||
@@ -46,9 +40,9 @@ export function hasProjectPermissions<T>(
|
||||
return Object.values(request).some((list) => list?.some(include))
|
||||
}
|
||||
|
||||
export const childMapByParent = (sessions: Session[] | undefined) => {
|
||||
export const childMapByParent = (sessions: Session[]) => {
|
||||
const map = new Map<string, string[]>()
|
||||
for (const session of sessions ?? []) {
|
||||
for (const session of sessions) {
|
||||
if (!session.parentID) continue
|
||||
const existing = map.get(session.parentID)
|
||||
if (existing) {
|
||||
|
||||
@@ -332,13 +332,12 @@ export const SortableWorkspace = (props: {
|
||||
const open = createMemo(() => props.ctx.workspaceExpanded(props.directory, local()))
|
||||
const boot = createMemo(() => open() || active())
|
||||
const booted = createMemo((prev) => prev || workspaceStore.status === "complete", false)
|
||||
const count = createMemo(() => sessions()?.length ?? 0)
|
||||
const hasMore = createMemo(() => workspaceStore.sessionTotal > count())
|
||||
const hasMore = createMemo(() => workspaceStore.sessionTotal > sessions().length)
|
||||
const busy = createMemo(() => props.ctx.isBusy(props.directory))
|
||||
const wasBusy = createMemo((prev) => prev || busy(), false)
|
||||
const loading = createMemo(() => open() && !booted() && count() === 0 && !wasBusy())
|
||||
const loading = createMemo(() => open() && !booted() && sessions().length === 0 && !wasBusy())
|
||||
const touch = createMediaQuery("(hover: none)")
|
||||
const showNew = createMemo(() => !loading() && (touch() || count() === 0 || (active() && !params.id)))
|
||||
const showNew = createMemo(() => !loading() && (touch() || sessions().length === 0 || (active() && !params.id)))
|
||||
const loadMore = async () => {
|
||||
setWorkspaceStore("limit", (limit) => (limit ?? 0) + 5)
|
||||
await globalSync.project.loadSessions(props.directory)
|
||||
@@ -473,9 +472,8 @@ export const LocalWorkspace = (props: {
|
||||
const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow()))
|
||||
const children = createMemo(() => childMapByParent(workspace().store.session))
|
||||
const booted = createMemo((prev) => prev || workspace().store.status === "complete", false)
|
||||
const count = createMemo(() => sessions()?.length ?? 0)
|
||||
const loading = createMemo(() => !booted() && count() === 0)
|
||||
const hasMore = createMemo(() => workspace().store.sessionTotal > count())
|
||||
const loading = createMemo(() => !booted() && sessions().length === 0)
|
||||
const hasMore = createMemo(() => workspace().store.sessionTotal > sessions().length)
|
||||
const loadMore = async () => {
|
||||
workspace().setStore("limit", (limit) => (limit ?? 0) + 5)
|
||||
await globalSync.project.loadSessions(props.project.worktree)
|
||||
|
||||
@@ -280,24 +280,21 @@ export function TerminalPanel() {
|
||||
</Tabs>
|
||||
<div class="flex-1 min-h-0 relative">
|
||||
<Show when={terminal.active()} keyed>
|
||||
{(id) => {
|
||||
const ops = terminal.bind()
|
||||
return (
|
||||
<Show when={all().find((pty) => pty.id === id)}>
|
||||
{(pty) => (
|
||||
<div id={`terminal-wrapper-${id}`} class="absolute inset-0">
|
||||
<Terminal
|
||||
pty={pty()}
|
||||
autoFocus={opened()}
|
||||
onConnect={() => ops.trim(id)}
|
||||
onCleanup={ops.update}
|
||||
onConnectError={() => ops.clone(id)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
)
|
||||
}}
|
||||
{(id) => (
|
||||
<Show when={all().find((pty) => pty.id === id)}>
|
||||
{(pty) => (
|
||||
<div id={`terminal-wrapper-${id}`} class="absolute inset-0">
|
||||
<Terminal
|
||||
pty={pty()}
|
||||
autoFocus={opened()}
|
||||
onConnect={() => terminal.trim(id)}
|
||||
onCleanup={terminal.update}
|
||||
onConnectError={() => terminal.clone(id)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Show>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -76,19 +76,6 @@ export function IconAlipay(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
)
|
||||
}
|
||||
|
||||
export function IconUpi(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} viewBox="10 16 100 28" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M95.678 42.9 110 29.835l-6.784-13.516Z" />
|
||||
<path d="M90.854 42.9 105.176 29.835l-6.784-13.516Z" />
|
||||
<path
|
||||
d="M22.41 16.47 16.38 37.945l21.407.15 5.88-21.625h5.427l-7.05 25.14c-.27.96-1.298 1.74-2.295 1.74H12.31c-1.664 0-2.65-1.3-2.2-2.9l6.724-23.98Zm66.182-.15h5.427l-7.538 27.03h-5.58ZM49.698 27.582l27.136-.15 1.81-5.707H51.054l1.658-5.256 29.4-.27c1.83-.017 2.92 1.4 2.438 3.167L81.78 29.49c-.483 1.766-2.36 3.197-4.19 3.197H53.316L50.454 43.8h-5.28Z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconWechat(props: JSX.SvgSVGAttributes<SVGSVGElement>) {
|
||||
return (
|
||||
<svg {...props} viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
@@ -62,6 +62,5 @@
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,8 +644,6 @@ export const dict = {
|
||||
"تم تصميم الخطة بشكل أساسي للمستخدمين الدوليين، مع استضافة النماذج في الولايات المتحدة والاتحاد الأوروبي وسنغافورة للحصول على وصول عالمي مستقر. قد تتغير الأسعار وحدود الاستخدام بناءً على تعلمنا من الاستخدام المبكر والملاحظات.",
|
||||
"workspace.lite.promo.subscribe": "الاشتراك في Go",
|
||||
"workspace.lite.promo.subscribing": "جارٍ إعادة التوجيه...",
|
||||
"workspace.lite.promo.otherMethods": "طرق دفع أخرى",
|
||||
"workspace.lite.promo.selectMethod": "اختر طريقة الدفع",
|
||||
|
||||
"download.title": "OpenCode | تنزيل",
|
||||
"download.meta.description": "نزّل OpenCode لـ macOS، Windows، وLinux",
|
||||
|
||||
@@ -654,8 +654,6 @@ export const dict = {
|
||||
"O plano é projetado principalmente para usuários internacionais, com modelos hospedados nos EUA, UE e Singapura para acesso global estável. Preços e limites de uso podem mudar conforme aprendemos com o uso inicial e feedback.",
|
||||
"workspace.lite.promo.subscribe": "Assinar Go",
|
||||
"workspace.lite.promo.subscribing": "Redirecionando...",
|
||||
"workspace.lite.promo.otherMethods": "Outros métodos de pagamento",
|
||||
"workspace.lite.promo.selectMethod": "Selecionar método de pagamento",
|
||||
|
||||
"download.title": "OpenCode | Baixar",
|
||||
"download.meta.description": "Baixe o OpenCode para macOS, Windows e Linux",
|
||||
|
||||
@@ -651,8 +651,6 @@ export const dict = {
|
||||
"Planen er primært designet til internationale brugere, med modeller hostet i USA, EU og Singapore for stabil global adgang. Priser og forbrugsgrænser kan ændre sig, efterhånden som vi lærer af tidlig brug og feedback.",
|
||||
"workspace.lite.promo.subscribe": "Abonner på Go",
|
||||
"workspace.lite.promo.subscribing": "Omdirigerer...",
|
||||
"workspace.lite.promo.otherMethods": "Andre betalingsmetoder",
|
||||
"workspace.lite.promo.selectMethod": "Vælg betalingsmetode",
|
||||
|
||||
"download.title": "OpenCode | Download",
|
||||
"download.meta.description": "Download OpenCode til macOS, Windows og Linux",
|
||||
|
||||
@@ -654,8 +654,6 @@ export const dict = {
|
||||
"Der Plan wurde hauptsächlich für internationale Nutzer entwickelt, wobei die Modelle in den USA, der EU und Singapur gehostet werden, um einen stabilen weltweiten Zugriff zu gewährleisten. Preise und Nutzungslimits können sich ändern, während wir aus der frühen Nutzung und dem Feedback lernen.",
|
||||
"workspace.lite.promo.subscribe": "Go abonnieren",
|
||||
"workspace.lite.promo.subscribing": "Leite weiter...",
|
||||
"workspace.lite.promo.otherMethods": "Andere Zahlungsmethoden",
|
||||
"workspace.lite.promo.selectMethod": "Zahlungsmethode auswählen",
|
||||
|
||||
"download.title": "OpenCode | Download",
|
||||
"download.meta.description": "Lade OpenCode für macOS, Windows und Linux herunter",
|
||||
|
||||
@@ -646,8 +646,6 @@ export const dict = {
|
||||
"The plan is designed primarily for international users, with models hosted in the US, EU, and Singapore for stable global access. Pricing and usage limits may change as we learn from early usage and feedback.",
|
||||
"workspace.lite.promo.subscribe": "Subscribe to Go",
|
||||
"workspace.lite.promo.subscribing": "Redirecting...",
|
||||
"workspace.lite.promo.otherMethods": "Other payment methods",
|
||||
"workspace.lite.promo.selectMethod": "Select payment method",
|
||||
|
||||
"download.title": "OpenCode | Download",
|
||||
"download.meta.description": "Download OpenCode for macOS, Windows, and Linux",
|
||||
|
||||
@@ -654,8 +654,6 @@ export const dict = {
|
||||
"El plan está diseñado principalmente para usuarios internacionales, con modelos alojados en EE. UU., la UE y Singapur para un acceso global estable. Los precios y los límites de uso pueden cambiar a medida que aprendemos del uso inicial y los comentarios.",
|
||||
"workspace.lite.promo.subscribe": "Suscribirse a Go",
|
||||
"workspace.lite.promo.subscribing": "Redirigiendo...",
|
||||
"workspace.lite.promo.otherMethods": "Otros métodos de pago",
|
||||
"workspace.lite.promo.selectMethod": "Seleccionar método de pago",
|
||||
|
||||
"download.title": "OpenCode | Descargar",
|
||||
"download.meta.description": "Descarga OpenCode para macOS, Windows y Linux",
|
||||
|
||||
@@ -661,8 +661,6 @@ export const dict = {
|
||||
"Le plan est conçu principalement pour les utilisateurs internationaux, avec des modèles hébergés aux États-Unis, dans l'UE et à Singapour pour un accès mondial stable. Les tarifs et les limites d'utilisation peuvent changer à mesure que nous apprenons des premières utilisations et des commentaires.",
|
||||
"workspace.lite.promo.subscribe": "S'abonner à Go",
|
||||
"workspace.lite.promo.subscribing": "Redirection...",
|
||||
"workspace.lite.promo.otherMethods": "Autres méthodes de paiement",
|
||||
"workspace.lite.promo.selectMethod": "Sélectionner la méthode de paiement",
|
||||
|
||||
"download.title": "OpenCode | Téléchargement",
|
||||
"download.meta.description": "Téléchargez OpenCode pour macOS, Windows et Linux",
|
||||
|
||||
@@ -652,8 +652,6 @@ export const dict = {
|
||||
"Il piano è progettato principalmente per gli utenti internazionali, con modelli ospitati in US, EU e Singapore per un accesso globale stabile. I prezzi e i limiti di utilizzo potrebbero cambiare man mano che impariamo dall'utilizzo iniziale e dal feedback.",
|
||||
"workspace.lite.promo.subscribe": "Abbonati a Go",
|
||||
"workspace.lite.promo.subscribing": "Reindirizzamento...",
|
||||
"workspace.lite.promo.otherMethods": "Altri metodi di pagamento",
|
||||
"workspace.lite.promo.selectMethod": "Seleziona metodo di pagamento",
|
||||
|
||||
"download.title": "OpenCode | Download",
|
||||
"download.meta.description": "Scarica OpenCode per macOS, Windows e Linux",
|
||||
|
||||
@@ -653,8 +653,6 @@ export const dict = {
|
||||
"このプランは主にグローバルユーザー向けに設計されており、米国、EU、シンガポールでホストされたモデルにより安定したグローバルアクセスを提供します。料金と利用制限は、初期の利用状況やフィードバックに基づいて変更される可能性があります。",
|
||||
"workspace.lite.promo.subscribe": "Goを購読する",
|
||||
"workspace.lite.promo.subscribing": "リダイレクト中...",
|
||||
"workspace.lite.promo.otherMethods": "その他の支払い方法",
|
||||
"workspace.lite.promo.selectMethod": "支払い方法を選択",
|
||||
|
||||
"download.title": "OpenCode | ダウンロード",
|
||||
"download.meta.description": "OpenCode を macOS、Windows、Linux 向けにダウンロード",
|
||||
|
||||
@@ -645,8 +645,6 @@ export const dict = {
|
||||
"이 플랜은 주로 글로벌 사용자를 위해 설계되었으며, 안정적인 글로벌 액세스를 위해 미국, EU 및 싱가포르에 모델이 호스팅되어 있습니다. 가격 및 사용 한도는 초기 사용을 통해 학습하고 피드백을 수집함에 따라 변경될 수 있습니다.",
|
||||
"workspace.lite.promo.subscribe": "Go 구독하기",
|
||||
"workspace.lite.promo.subscribing": "리디렉션 중...",
|
||||
"workspace.lite.promo.otherMethods": "기타 결제 수단",
|
||||
"workspace.lite.promo.selectMethod": "결제 수단 선택",
|
||||
|
||||
"download.title": "OpenCode | 다운로드",
|
||||
"download.meta.description": "macOS, Windows, Linux용 OpenCode 다운로드",
|
||||
|
||||
@@ -651,8 +651,6 @@ export const dict = {
|
||||
"Planen er primært designet for internasjonale brukere, med modeller driftet i USA, EU og Singapore for stabil global tilgang. Priser og bruksgrenser kan endres etter hvert som vi lærer fra tidlig bruk og tilbakemeldinger.",
|
||||
"workspace.lite.promo.subscribe": "Abonner på Go",
|
||||
"workspace.lite.promo.subscribing": "Omdirigerer...",
|
||||
"workspace.lite.promo.otherMethods": "Andre betalingsmetoder",
|
||||
"workspace.lite.promo.selectMethod": "Velg betalingsmetode",
|
||||
|
||||
"download.title": "OpenCode | Last ned",
|
||||
"download.meta.description": "Last ned OpenCode for macOS, Windows og Linux",
|
||||
|
||||
@@ -652,8 +652,6 @@ export const dict = {
|
||||
"Plan został zaprojektowany głównie dla użytkowników międzynarodowych, z modelami hostowanymi w USA, UE i Singapurze, aby zapewnić stabilny globalny dostęp. Ceny i limity użycia mogą ulec zmianie w miarę analizy wczesnego użycia i zbierania opinii.",
|
||||
"workspace.lite.promo.subscribe": "Subskrybuj Go",
|
||||
"workspace.lite.promo.subscribing": "Przekierowywanie...",
|
||||
"workspace.lite.promo.otherMethods": "Inne metody płatności",
|
||||
"workspace.lite.promo.selectMethod": "Wybierz metodę płatności",
|
||||
|
||||
"download.title": "OpenCode | Pobierz",
|
||||
"download.meta.description": "Pobierz OpenCode na macOS, Windows i Linux",
|
||||
|
||||
@@ -658,8 +658,6 @@ export const dict = {
|
||||
"План предназначен в первую очередь для международных пользователей. Модели размещены в США, ЕС и Сингапуре для стабильного глобального доступа. Цены и лимиты использования могут меняться по мере того, как мы изучаем раннее использование и собираем отзывы.",
|
||||
"workspace.lite.promo.subscribe": "Подписаться на Go",
|
||||
"workspace.lite.promo.subscribing": "Перенаправление...",
|
||||
"workspace.lite.promo.otherMethods": "Другие способы оплаты",
|
||||
"workspace.lite.promo.selectMethod": "Выберите способ оплаты",
|
||||
|
||||
"download.title": "OpenCode | Скачать",
|
||||
"download.meta.description": "Скачать OpenCode для macOS, Windows и Linux",
|
||||
|
||||
@@ -648,8 +648,6 @@ export const dict = {
|
||||
"แผนนี้ออกแบบมาสำหรับผู้ใช้งานต่างประเทศเป็นหลัก โดยมีโมเดลโฮสต์อยู่ในสหรัฐอเมริกา สหภาพยุโรป และสิงคโปร์ เพื่อการเข้าถึงที่เสถียรทั่วโลก ราคาและขีดจำกัดการใช้งานอาจมีการเปลี่ยนแปลงตามที่เราได้เรียนรู้จากการใช้งานในช่วงแรกและข้อเสนอแนะ",
|
||||
"workspace.lite.promo.subscribe": "สมัครสมาชิก Go",
|
||||
"workspace.lite.promo.subscribing": "กำลังเปลี่ยนเส้นทาง...",
|
||||
"workspace.lite.promo.otherMethods": "วิธีการชำระเงินอื่นๆ",
|
||||
"workspace.lite.promo.selectMethod": "เลือกวิธีการชำระเงิน",
|
||||
|
||||
"download.title": "OpenCode | ดาวน์โหลด",
|
||||
"download.meta.description": "ดาวน์โหลด OpenCode สำหรับ macOS, Windows และ Linux",
|
||||
|
||||
@@ -655,8 +655,6 @@ export const dict = {
|
||||
"Plan öncelikle uluslararası kullanıcılar için tasarlanmıştır; modeller istikrarlı küresel erişim için ABD, AB ve Singapur'da barındırılmaktadır. Erken kullanımdan öğrendikçe ve geri bildirim topladıkça fiyatlandırma ve kullanım limitleri değişebilir.",
|
||||
"workspace.lite.promo.subscribe": "Go'ya Abone Ol",
|
||||
"workspace.lite.promo.subscribing": "Yönlendiriliyor...",
|
||||
"workspace.lite.promo.otherMethods": "Diğer ödeme yöntemleri",
|
||||
"workspace.lite.promo.selectMethod": "Ödeme yöntemini seçin",
|
||||
|
||||
"download.title": "OpenCode | İndir",
|
||||
"download.meta.description": "OpenCode'u macOS, Windows ve Linux için indirin",
|
||||
|
||||
@@ -626,8 +626,6 @@ export const dict = {
|
||||
"该计划主要面向国际用户设计,模型部署在美国、欧盟和新加坡,以确保全球范围内的稳定访问体验。定价和使用额度可能会根据早期用户的使用情况和反馈持续调整与优化。",
|
||||
"workspace.lite.promo.subscribe": "订阅 Go",
|
||||
"workspace.lite.promo.subscribing": "正在重定向...",
|
||||
"workspace.lite.promo.otherMethods": "其他付款方式",
|
||||
"workspace.lite.promo.selectMethod": "选择付款方式",
|
||||
|
||||
"download.title": "OpenCode | 下载",
|
||||
"download.meta.description": "下载适用于 macOS, Windows, 和 Linux 的 OpenCode",
|
||||
|
||||
@@ -626,8 +626,6 @@ export const dict = {
|
||||
"該計畫主要面向國際用戶設計,模型部署在美國、歐盟和新加坡,以確保全球範圍內的穩定存取體驗。定價和使用額度可能會根據早期用戶的使用情況和回饋持續調整與優化。",
|
||||
"workspace.lite.promo.subscribe": "訂閱 Go",
|
||||
"workspace.lite.promo.subscribing": "重新導向中...",
|
||||
"workspace.lite.promo.otherMethods": "其他付款方式",
|
||||
"workspace.lite.promo.selectMethod": "選擇付款方式",
|
||||
|
||||
"download.title": "OpenCode | 下載",
|
||||
"download.meta.description": "下載適用於 macOS、Windows 與 Linux 的 OpenCode",
|
||||
|
||||
@@ -244,7 +244,6 @@ export async function POST(input: APIEvent) {
|
||||
customerID,
|
||||
enrichment: {
|
||||
type: productID === LiteData.productID() ? "lite" : "subscription",
|
||||
currency: body.data.object.currency === "inr" ? "inr" : undefined,
|
||||
couponID,
|
||||
},
|
||||
}),
|
||||
@@ -332,17 +331,16 @@ export async function POST(input: APIEvent) {
|
||||
)
|
||||
if (!workspaceID) throw new Error("Workspace ID not found")
|
||||
|
||||
const payment = await Database.use((tx) =>
|
||||
const amount = await Database.use((tx) =>
|
||||
tx
|
||||
.select({
|
||||
amount: PaymentTable.amount,
|
||||
enrichment: PaymentTable.enrichment,
|
||||
})
|
||||
.from(PaymentTable)
|
||||
.where(and(eq(PaymentTable.paymentID, paymentIntentID), eq(PaymentTable.workspaceID, workspaceID)))
|
||||
.then((rows) => rows[0]),
|
||||
.then((rows) => rows[0]?.amount),
|
||||
)
|
||||
if (!payment) throw new Error("Payment not found")
|
||||
if (!amount) throw new Error("Payment not found")
|
||||
|
||||
await Database.transaction(async (tx) => {
|
||||
await tx
|
||||
@@ -352,15 +350,12 @@ export async function POST(input: APIEvent) {
|
||||
})
|
||||
.where(and(eq(PaymentTable.paymentID, paymentIntentID), eq(PaymentTable.workspaceID, workspaceID)))
|
||||
|
||||
// deduct balance only for top up
|
||||
if (!payment.enrichment?.type) {
|
||||
await tx
|
||||
.update(BillingTable)
|
||||
.set({
|
||||
balance: sql`${BillingTable.balance} - ${payment.amount}`,
|
||||
})
|
||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||
}
|
||||
await tx
|
||||
.update(BillingTable)
|
||||
.set({
|
||||
balance: sql`${BillingTable.balance} - ${amount}`,
|
||||
})
|
||||
.where(eq(BillingTable.workspaceID, workspaceID))
|
||||
})
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -3,7 +3,7 @@ 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 { IconAlipay, IconCreditCard, IconStripe, IconUpi, IconWechat } from "~/component/icon"
|
||||
import { IconAlipay, IconCreditCard, IconStripe, IconWechat } from "~/component/icon"
|
||||
import styles from "./billing-section.module.css"
|
||||
import { createCheckoutUrl, formatBalance, queryBillingInfo } from "../../common"
|
||||
import { useI18n } from "~/context/i18n"
|
||||
@@ -211,9 +211,6 @@ export function BillingSection() {
|
||||
<Match when={billingInfo()?.paymentMethodType === "wechat_pay"}>
|
||||
<IconWechat style={{ width: "24px", height: "24px" }} />
|
||||
</Match>
|
||||
<Match when={billingInfo()?.paymentMethodType === "upi"}>
|
||||
<IconUpi style={{ width: "auto", height: "16px" }} />
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
<div data-slot="card-details">
|
||||
|
||||
@@ -6,14 +6,6 @@ import { formatDateUTC, formatDateForTable } from "../../common"
|
||||
import styles from "./payment-section.module.css"
|
||||
import { useI18n } from "~/context/i18n"
|
||||
|
||||
function money(amount: number, currency?: string) {
|
||||
const formatter =
|
||||
currency === "inr"
|
||||
? new Intl.NumberFormat("en-IN", { style: "currency", currency: "INR" })
|
||||
: new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" })
|
||||
return formatter.format(amount / 100_000_000)
|
||||
}
|
||||
|
||||
const getPaymentsInfo = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
@@ -89,10 +81,6 @@ export function PaymentSection() {
|
||||
const date = new Date(payment.timeCreated)
|
||||
const amount =
|
||||
payment.enrichment?.type === "subscription" && payment.enrichment.couponID ? 0 : payment.amount
|
||||
const currency =
|
||||
payment.enrichment?.type === "subscription" || payment.enrichment?.type === "lite"
|
||||
? payment.enrichment.currency
|
||||
: undefined
|
||||
return (
|
||||
<tr>
|
||||
<td data-slot="payment-date" title={formatDateUTC(date)}>
|
||||
@@ -100,7 +88,7 @@ export function PaymentSection() {
|
||||
</td>
|
||||
<td data-slot="payment-id">{payment.id}</td>
|
||||
<td data-slot="payment-amount" data-refunded={!!payment.timeRefunded}>
|
||||
{money(amount, currency)}
|
||||
${((amount ?? 0) / 100000000).toFixed(2)}
|
||||
<Switch>
|
||||
<Match when={payment.enrichment?.type === "credit"}>
|
||||
{" "}
|
||||
|
||||
@@ -188,45 +188,8 @@
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
[data-slot="subscribe-actions"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
|
||||
[data-slot="subscribe-button"] {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
[data-slot="other-methods"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
[data-slot="other-methods-icons"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
[data-slot="modal-actions"] {
|
||||
display: flex;
|
||||
gap: var(--space-3);
|
||||
align-self: flex-start;
|
||||
margin-top: var(--space-4);
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="method-button"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: var(--space-2);
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { action, useParams, useAction, useSubmission, json, query, createAsync } from "@solidjs/router"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { createMemo, For, Show } from "solid-js"
|
||||
import { Modal } from "~/component/modal"
|
||||
import { Billing } from "@opencode-ai/console-core/billing.js"
|
||||
import { Database, eq, and, isNull } from "@opencode-ai/console-core/drizzle/index.js"
|
||||
import { BillingTable, LiteTable } from "@opencode-ai/console-core/schema/billing.sql.js"
|
||||
@@ -15,8 +14,6 @@ import { useI18n } from "~/context/i18n"
|
||||
import { useLanguage } from "~/context/language"
|
||||
import { formError } from "~/lib/form-error"
|
||||
|
||||
import { IconAlipay, IconUpi } from "~/component/icon"
|
||||
|
||||
const queryLiteSubscription = query(async (workspaceID: string) => {
|
||||
"use server"
|
||||
return withActor(async () => {
|
||||
@@ -81,25 +78,22 @@ function formatResetTime(seconds: number, i18n: ReturnType<typeof useI18n>) {
|
||||
return `${minutes} ${minutes === 1 ? i18n.t("workspace.lite.time.minute") : i18n.t("workspace.lite.time.minutes")}`
|
||||
}
|
||||
|
||||
const createLiteCheckoutUrl = action(
|
||||
async (workspaceID: string, successUrl: string, cancelUrl: string, method?: "alipay" | "upi") => {
|
||||
"use server"
|
||||
return json(
|
||||
await withActor(
|
||||
() =>
|
||||
Billing.generateLiteCheckoutUrl({ successUrl, cancelUrl, method })
|
||||
.then((data) => ({ error: undefined, data }))
|
||||
.catch((e) => ({
|
||||
error: e.message as string,
|
||||
data: undefined,
|
||||
})),
|
||||
workspaceID,
|
||||
),
|
||||
{ revalidate: [queryBillingInfo.key, queryLiteSubscription.key] },
|
||||
)
|
||||
},
|
||||
"liteCheckoutUrl",
|
||||
)
|
||||
const createLiteCheckoutUrl = action(async (workspaceID: string, successUrl: string, cancelUrl: string) => {
|
||||
"use server"
|
||||
return json(
|
||||
await withActor(
|
||||
() =>
|
||||
Billing.generateLiteCheckoutUrl({ successUrl, cancelUrl })
|
||||
.then((data) => ({ error: undefined, data }))
|
||||
.catch((e) => ({
|
||||
error: e.message as string,
|
||||
data: undefined,
|
||||
})),
|
||||
workspaceID,
|
||||
),
|
||||
{ revalidate: [queryBillingInfo.key, queryLiteSubscription.key] },
|
||||
)
|
||||
}, "liteCheckoutUrl")
|
||||
|
||||
const createSessionUrl = action(async (workspaceID: string, returnUrl: string) => {
|
||||
"use server"
|
||||
@@ -153,30 +147,23 @@ export function LiteSection() {
|
||||
const checkoutSubmission = useSubmission(createLiteCheckoutUrl)
|
||||
const useBalanceSubmission = useSubmission(setLiteUseBalance)
|
||||
const [store, setStore] = createStore({
|
||||
loading: undefined as undefined | "session" | "checkout" | "alipay" | "upi",
|
||||
showModal: false,
|
||||
redirecting: false,
|
||||
})
|
||||
|
||||
const busy = createMemo(() => !!store.loading)
|
||||
|
||||
async function onClickSession() {
|
||||
setStore("loading", "session")
|
||||
const result = await sessionAction(params.id!, window.location.href)
|
||||
if (result.data) {
|
||||
setStore("redirecting", true)
|
||||
window.location.href = result.data
|
||||
return
|
||||
}
|
||||
setStore("loading", undefined)
|
||||
}
|
||||
|
||||
async function onClickSubscribe(method?: "alipay" | "upi") {
|
||||
setStore("loading", method ?? "checkout")
|
||||
const result = await checkoutAction(params.id!, window.location.href, window.location.href, method)
|
||||
async function onClickSubscribe() {
|
||||
const result = await checkoutAction(params.id!, window.location.href, window.location.href)
|
||||
if (result.data) {
|
||||
setStore("redirecting", true)
|
||||
window.location.href = result.data
|
||||
return
|
||||
}
|
||||
setStore("loading", undefined)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -192,8 +179,12 @@ export function LiteSection() {
|
||||
<div data-slot="section-title">
|
||||
<div data-slot="title-row">
|
||||
<p>{i18n.t("workspace.lite.subscription.message")}</p>
|
||||
<button data-color="primary" disabled={sessionSubmission.pending || busy()} onClick={onClickSession}>
|
||||
{store.loading === "session"
|
||||
<button
|
||||
data-color="primary"
|
||||
disabled={sessionSubmission.pending || store.redirecting}
|
||||
onClick={onClickSession}
|
||||
>
|
||||
{sessionSubmission.pending || store.redirecting
|
||||
? i18n.t("workspace.lite.loading")
|
||||
: i18n.t("workspace.lite.subscription.manage")}
|
||||
</button>
|
||||
@@ -291,64 +282,16 @@ export function LiteSection() {
|
||||
<li>MiniMax M2.7</li>
|
||||
</ul>
|
||||
<p data-slot="promo-description">{i18n.t("workspace.lite.promo.footer")}</p>
|
||||
<div data-slot="subscribe-actions">
|
||||
<button
|
||||
data-slot="subscribe-button"
|
||||
data-color="primary"
|
||||
disabled={checkoutSubmission.pending || busy()}
|
||||
onClick={() => onClickSubscribe()}
|
||||
>
|
||||
{store.loading === "checkout"
|
||||
? i18n.t("workspace.lite.promo.subscribing")
|
||||
: i18n.t("workspace.lite.promo.subscribe")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-slot="other-methods"
|
||||
data-color="ghost"
|
||||
onClick={() => setStore("showModal", true)}
|
||||
>
|
||||
<span>{i18n.t("workspace.lite.promo.otherMethods")}</span>
|
||||
<span data-slot="other-methods-icons">
|
||||
<span> </span>
|
||||
<IconAlipay style={{ width: "16px", height: "16px" }} />
|
||||
<span> </span>
|
||||
<IconUpi style={{ width: "auto", height: "10px" }} />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<Modal
|
||||
open={store.showModal}
|
||||
onClose={() => setStore("showModal", false)}
|
||||
title={i18n.t("workspace.lite.promo.selectMethod")}
|
||||
<button
|
||||
data-slot="subscribe-button"
|
||||
data-color="primary"
|
||||
disabled={checkoutSubmission.pending || store.redirecting}
|
||||
onClick={onClickSubscribe}
|
||||
>
|
||||
<div data-slot="modal-actions">
|
||||
<button
|
||||
type="button"
|
||||
data-slot="method-button"
|
||||
data-color="ghost"
|
||||
disabled={checkoutSubmission.pending || busy()}
|
||||
onClick={() => onClickSubscribe("alipay")}
|
||||
>
|
||||
<Show when={store.loading !== "alipay"}>
|
||||
<IconAlipay style={{ width: "24px", height: "24px" }} />
|
||||
</Show>
|
||||
{store.loading === "alipay" ? i18n.t("workspace.lite.promo.subscribing") : "Alipay"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-slot="method-button"
|
||||
data-color="ghost"
|
||||
disabled={checkoutSubmission.pending || busy()}
|
||||
onClick={() => onClickSubscribe("upi")}
|
||||
>
|
||||
<Show when={store.loading !== "upi"}>
|
||||
<IconUpi style={{ width: "auto", height: "16px" }} />
|
||||
</Show>
|
||||
{store.loading === "upi" ? i18n.t("workspace.lite.promo.subscribing") : "UPI"}
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
{checkoutSubmission.pending || store.redirecting
|
||||
? i18n.t("workspace.lite.promo.subscribing")
|
||||
: i18n.t("workspace.lite.promo.subscribe")}
|
||||
</button>
|
||||
</section>
|
||||
</Show>
|
||||
</>
|
||||
|
||||
@@ -239,11 +239,10 @@ export namespace Billing {
|
||||
z.object({
|
||||
successUrl: z.string(),
|
||||
cancelUrl: z.string(),
|
||||
method: z.enum(["alipay", "upi"]).optional(),
|
||||
}),
|
||||
async (input) => {
|
||||
const user = Actor.assert("user")
|
||||
const { successUrl, cancelUrl, method } = input
|
||||
const { successUrl, cancelUrl } = input
|
||||
|
||||
const email = await User.getAuthEmail(user.properties.userID)
|
||||
const billing = await Billing.get()
|
||||
@@ -251,102 +250,38 @@ export namespace Billing {
|
||||
if (billing.subscriptionID) throw new Error("Already subscribed to Black")
|
||||
if (billing.liteSubscriptionID) throw new Error("Already subscribed to Lite")
|
||||
|
||||
const createSession = () =>
|
||||
Billing.stripe().checkout.sessions.create({
|
||||
mode: "subscription",
|
||||
discounts: [{ coupon: LiteData.firstMonth50Coupon() }],
|
||||
...(billing.customerID
|
||||
? {
|
||||
customer: billing.customerID,
|
||||
customer_update: {
|
||||
name: "auto",
|
||||
address: "auto",
|
||||
},
|
||||
}
|
||||
: {
|
||||
customer_email: email!,
|
||||
}),
|
||||
...(() => {
|
||||
if (method === "alipay") {
|
||||
return {
|
||||
line_items: [{ price: LiteData.priceID(), quantity: 1 }],
|
||||
payment_method_types: ["alipay"],
|
||||
adaptive_pricing: {
|
||||
enabled: false,
|
||||
},
|
||||
}
|
||||
const session = await Billing.stripe().checkout.sessions.create({
|
||||
mode: "subscription",
|
||||
billing_address_collection: "required",
|
||||
line_items: [{ price: LiteData.priceID(), quantity: 1 }],
|
||||
discounts: [{ coupon: LiteData.firstMonth50Coupon() }],
|
||||
...(billing.customerID
|
||||
? {
|
||||
customer: billing.customerID,
|
||||
customer_update: {
|
||||
name: "auto",
|
||||
address: "auto",
|
||||
},
|
||||
}
|
||||
if (method === "upi") {
|
||||
return {
|
||||
line_items: [
|
||||
{
|
||||
price_data: {
|
||||
currency: "inr",
|
||||
product: LiteData.productID(),
|
||||
recurring: {
|
||||
interval: "month",
|
||||
interval_count: 1,
|
||||
},
|
||||
unit_amount: LiteData.priceInr(),
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
payment_method_types: ["upi"] as any,
|
||||
adaptive_pricing: {
|
||||
enabled: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
return {
|
||||
line_items: [{ price: LiteData.priceID(), quantity: 1 }],
|
||||
billing_address_collection: "required",
|
||||
}
|
||||
})(),
|
||||
tax_id_collection: {
|
||||
enabled: true,
|
||||
: {
|
||||
customer_email: email!,
|
||||
}),
|
||||
currency: "usd",
|
||||
tax_id_collection: {
|
||||
enabled: true,
|
||||
},
|
||||
success_url: successUrl,
|
||||
cancel_url: cancelUrl,
|
||||
subscription_data: {
|
||||
metadata: {
|
||||
workspaceID: Actor.workspace(),
|
||||
userID: user.properties.userID,
|
||||
type: "lite",
|
||||
},
|
||||
success_url: successUrl,
|
||||
cancel_url: cancelUrl,
|
||||
subscription_data: {
|
||||
metadata: {
|
||||
workspaceID: Actor.workspace(),
|
||||
userID: user.properties.userID,
|
||||
type: "lite",
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
try {
|
||||
const session = await createSession()
|
||||
return session.url
|
||||
} catch (e: any) {
|
||||
if (
|
||||
e.type !== "StripeInvalidRequestError" ||
|
||||
!e.message.includes("You cannot combine currencies on a single customer")
|
||||
)
|
||||
throw e
|
||||
|
||||
// get pending payment intent
|
||||
const intents = await Billing.stripe().paymentIntents.search({
|
||||
query: `-status:'canceled' AND -status:'processing' AND -status:'succeeded' AND customer:'${billing.customerID}'`,
|
||||
})
|
||||
if (intents.data.length === 0) throw e
|
||||
|
||||
for (const intent of intents.data) {
|
||||
// get checkout session
|
||||
const sessions = await Billing.stripe().checkout.sessions.list({
|
||||
customer: billing.customerID!,
|
||||
payment_intent: intent.id,
|
||||
})
|
||||
|
||||
// delete pending payment intent
|
||||
await Billing.stripe().checkout.sessions.expire(sessions.data[0].id)
|
||||
}
|
||||
|
||||
const session = await createSession()
|
||||
return session.url
|
||||
}
|
||||
return session.url
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ export namespace LiteData {
|
||||
|
||||
export const productID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.product)
|
||||
export const priceID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.price)
|
||||
export const priceInr = fn(z.void(), () => Resource.ZEN_LITE_PRICE.priceInr)
|
||||
export const firstMonth50Coupon = fn(z.void(), () => Resource.ZEN_LITE_PRICE.firstMonth50Coupon)
|
||||
export const planName = fn(z.void(), () => "lite")
|
||||
}
|
||||
|
||||
@@ -88,7 +88,6 @@ export const PaymentTable = mysqlTable(
|
||||
enrichment: json("enrichment").$type<
|
||||
| {
|
||||
type: "subscription" | "lite"
|
||||
currency?: "inr"
|
||||
couponID?: string
|
||||
}
|
||||
| {
|
||||
|
||||
1
packages/console/core/sst-env.d.ts
vendored
1
packages/console/core/sst-env.d.ts
vendored
@@ -145,7 +145,6 @@ declare module "sst" {
|
||||
"ZEN_LITE_PRICE": {
|
||||
"firstMonth50Coupon": string
|
||||
"price": string
|
||||
"priceInr": number
|
||||
"product": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
|
||||
1
packages/console/function/sst-env.d.ts
vendored
1
packages/console/function/sst-env.d.ts
vendored
@@ -145,7 +145,6 @@ declare module "sst" {
|
||||
"ZEN_LITE_PRICE": {
|
||||
"firstMonth50Coupon": string
|
||||
"price": string
|
||||
"priceInr": number
|
||||
"product": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
|
||||
1
packages/console/resource/sst-env.d.ts
vendored
1
packages/console/resource/sst-env.d.ts
vendored
@@ -145,7 +145,6 @@ declare module "sst" {
|
||||
"ZEN_LITE_PRICE": {
|
||||
"firstMonth50Coupon": string
|
||||
"price": string
|
||||
"priceInr": number
|
||||
"product": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
|
||||
@@ -152,12 +152,12 @@ const createPlatform = (): Platform => {
|
||||
storage,
|
||||
|
||||
checkUpdate: async () => {
|
||||
if (!UPDATER_ENABLED()) return { updateAvailable: false }
|
||||
if (!UPDATER_ENABLED) return { updateAvailable: false }
|
||||
return window.api.checkUpdate()
|
||||
},
|
||||
|
||||
update: async () => {
|
||||
if (!UPDATER_ENABLED()) return
|
||||
if (!UPDATER_ENABLED) return
|
||||
await window.api.installUpdate()
|
||||
},
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { initI18n, t } from "./i18n"
|
||||
|
||||
export const UPDATER_ENABLED = () => window.__OPENCODE__?.updaterEnabled ?? false
|
||||
export const UPDATER_ENABLED = window.__OPENCODE__?.updaterEnabled ?? false
|
||||
|
||||
export async function runUpdater({ alertOnFail }: { alertOnFail: boolean }) {
|
||||
await initI18n()
|
||||
|
||||
1
packages/enterprise/sst-env.d.ts
vendored
1
packages/enterprise/sst-env.d.ts
vendored
@@ -145,7 +145,6 @@ declare module "sst" {
|
||||
"ZEN_LITE_PRICE": {
|
||||
"firstMonth50Coupon": string
|
||||
"price": string
|
||||
"priceInr": number
|
||||
"product": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
|
||||
1
packages/function/sst-env.d.ts
vendored
1
packages/function/sst-env.d.ts
vendored
@@ -145,7 +145,6 @@ declare module "sst" {
|
||||
"ZEN_LITE_PRICE": {
|
||||
"firstMonth50Coupon": string
|
||||
"price": string
|
||||
"priceInr": number
|
||||
"product": string
|
||||
"type": "sst.sst.Linkable"
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
"@types/bun": "catalog:",
|
||||
"@types/cross-spawn": "6.0.6",
|
||||
"@types/mime-types": "3.0.1",
|
||||
"@types/npmcli__arborist": "6.3.3",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/turndown": "5.0.5",
|
||||
"@types/which": "3.0.4",
|
||||
@@ -83,12 +82,11 @@
|
||||
"@ai-sdk/xai": "2.0.51",
|
||||
"@aws-sdk/credential-providers": "3.993.0",
|
||||
"@clack/prompts": "1.0.0-alpha.1",
|
||||
"@gitlab/gitlab-ai-provider": "3.6.0",
|
||||
"gitlab-ai-provider": "5.2.0",
|
||||
"@gitlab/opencode-gitlab-auth": "1.3.3",
|
||||
"@hono/standard-validator": "0.1.5",
|
||||
"@hono/zod-validator": "catalog:",
|
||||
"@modelcontextprotocol/sdk": "1.25.2",
|
||||
"@npmcli/arborist": "9.4.0",
|
||||
"@octokit/graphql": "9.0.2",
|
||||
"@octokit/rest": "catalog:",
|
||||
"@openauthjs/openauth": "catalog:",
|
||||
|
||||
@@ -148,12 +148,6 @@ export namespace AccountEffect {
|
||||
mapAccountServiceError("HTTP request failed"),
|
||||
)
|
||||
|
||||
const executeEffect = <E>(request: Effect.Effect<HttpClientRequest.HttpClientRequest, E>) =>
|
||||
request.pipe(
|
||||
Effect.flatMap((req) => http.execute(req)),
|
||||
mapAccountServiceError("HTTP request failed"),
|
||||
)
|
||||
|
||||
const resolveToken = Effect.fnUntraced(function* (row: AccountRow) {
|
||||
const now = yield* Clock.currentTimeMillis
|
||||
if (row.token_expiry && row.token_expiry > now) return row.access_token
|
||||
@@ -296,7 +290,7 @@ export namespace AccountEffect {
|
||||
})
|
||||
|
||||
const poll = Effect.fn("Account.poll")(function* (input: Login) {
|
||||
const response = yield* executeEffect(
|
||||
const response = yield* executeEffectOk(
|
||||
HttpClientRequest.post(`${input.server}/auth/device/token`).pipe(
|
||||
HttpClientRequest.acceptJson,
|
||||
HttpClientRequest.schemaBodyJson(DeviceTokenRequest)(
|
||||
|
||||
@@ -260,10 +260,7 @@ export namespace Agent {
|
||||
return pipe(
|
||||
await state(),
|
||||
values(),
|
||||
sortBy(
|
||||
[(x) => (cfg.default_agent ? x.name === cfg.default_agent : x.name === "build"), "desc"],
|
||||
[(x) => x.name, "asc"],
|
||||
),
|
||||
sortBy([(x) => (cfg.default_agent ? x.name === cfg.default_agent : x.name === "build"), "desc"]),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
127
packages/opencode/src/bun/index.ts
Normal file
127
packages/opencode/src/bun/index.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import z from "zod"
|
||||
import { Global } from "../global"
|
||||
import { Log } from "../util/log"
|
||||
import path from "path"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { NamedError } from "@opencode-ai/util/error"
|
||||
import { Lock } from "../util/lock"
|
||||
import { PackageRegistry } from "./registry"
|
||||
import { proxied } from "@/util/proxied"
|
||||
import { Process } from "../util/process"
|
||||
|
||||
export namespace BunProc {
|
||||
const log = Log.create({ service: "bun" })
|
||||
|
||||
export async function run(cmd: string[], options?: Process.RunOptions) {
|
||||
const full = [which(), ...cmd]
|
||||
log.info("running", {
|
||||
cmd: full,
|
||||
...options,
|
||||
})
|
||||
const result = await Process.run(full, {
|
||||
cwd: options?.cwd,
|
||||
abort: options?.abort,
|
||||
kill: options?.kill,
|
||||
timeout: options?.timeout,
|
||||
nothrow: options?.nothrow,
|
||||
env: {
|
||||
...process.env,
|
||||
...options?.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
})
|
||||
log.info("done", {
|
||||
code: result.code,
|
||||
stdout: result.stdout.toString(),
|
||||
stderr: result.stderr.toString(),
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
export function which() {
|
||||
return process.execPath
|
||||
}
|
||||
|
||||
export const InstallFailedError = NamedError.create(
|
||||
"BunInstallFailedError",
|
||||
z.object({
|
||||
pkg: z.string(),
|
||||
version: z.string(),
|
||||
}),
|
||||
)
|
||||
|
||||
export async function install(pkg: string, version = "latest") {
|
||||
// Use lock to ensure only one install at a time
|
||||
using _ = await Lock.write("bun-install")
|
||||
|
||||
const mod = path.join(Global.Path.cache, "node_modules", pkg)
|
||||
const pkgjsonPath = path.join(Global.Path.cache, "package.json")
|
||||
const parsed = await Filesystem.readJson<{ dependencies: Record<string, string> }>(pkgjsonPath).catch(async () => {
|
||||
const result = { dependencies: {} as Record<string, string> }
|
||||
await Filesystem.writeJson(pkgjsonPath, result)
|
||||
return result
|
||||
})
|
||||
if (!parsed.dependencies) parsed.dependencies = {} as Record<string, string>
|
||||
const dependencies = parsed.dependencies
|
||||
const modExists = await Filesystem.exists(mod)
|
||||
const cachedVersion = dependencies[pkg]
|
||||
|
||||
if (!modExists || !cachedVersion) {
|
||||
// continue to install
|
||||
} else if (version !== "latest" && cachedVersion === version) {
|
||||
return mod
|
||||
} else if (version === "latest") {
|
||||
const isOutdated = await PackageRegistry.isOutdated(pkg, cachedVersion, Global.Path.cache)
|
||||
if (!isOutdated) return mod
|
||||
log.info("Cached version is outdated, proceeding with install", { pkg, cachedVersion })
|
||||
}
|
||||
|
||||
// Build command arguments
|
||||
const args = [
|
||||
"add",
|
||||
"--force",
|
||||
"--exact",
|
||||
// TODO: get rid of this case (see: https://github.com/oven-sh/bun/issues/19936)
|
||||
...(proxied() || process.env.CI ? ["--no-cache"] : []),
|
||||
"--cwd",
|
||||
Global.Path.cache,
|
||||
pkg + "@" + version,
|
||||
]
|
||||
|
||||
// Let Bun handle registry resolution:
|
||||
// - If .npmrc files exist, Bun will use them automatically
|
||||
// - If no .npmrc files exist, Bun will default to https://registry.npmjs.org
|
||||
// - No need to pass --registry flag
|
||||
log.info("installing package using Bun's default registry resolution", {
|
||||
pkg,
|
||||
version,
|
||||
})
|
||||
|
||||
await BunProc.run(args, {
|
||||
cwd: Global.Path.cache,
|
||||
}).catch((e) => {
|
||||
throw new InstallFailedError(
|
||||
{ pkg, version },
|
||||
{
|
||||
cause: e,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
// Resolve actual version from installed package when using "latest"
|
||||
// This ensures subsequent starts use the cached version until explicitly updated
|
||||
let resolvedVersion = version
|
||||
if (version === "latest") {
|
||||
const installedPkg = await Filesystem.readJson<{ version?: string }>(path.join(mod, "package.json")).catch(
|
||||
() => null,
|
||||
)
|
||||
if (installedPkg?.version) {
|
||||
resolvedVersion = installedPkg.version
|
||||
}
|
||||
}
|
||||
|
||||
parsed.dependencies[pkg] = resolvedVersion
|
||||
await Filesystem.writeJson(pkgjsonPath, parsed)
|
||||
return mod
|
||||
}
|
||||
}
|
||||
44
packages/opencode/src/bun/registry.ts
Normal file
44
packages/opencode/src/bun/registry.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import semver from "semver"
|
||||
import { Log } from "../util/log"
|
||||
import { Process } from "../util/process"
|
||||
|
||||
export namespace PackageRegistry {
|
||||
const log = Log.create({ service: "bun" })
|
||||
|
||||
function which() {
|
||||
return process.execPath
|
||||
}
|
||||
|
||||
export async function info(pkg: string, field: string, cwd?: string): Promise<string | null> {
|
||||
const { code, stdout, stderr } = await Process.run([which(), "info", pkg, field], {
|
||||
cwd,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
nothrow: true,
|
||||
})
|
||||
|
||||
if (code !== 0) {
|
||||
log.warn("bun info failed", { pkg, field, code, stderr: stderr.toString() })
|
||||
return null
|
||||
}
|
||||
|
||||
const value = stdout.toString().trim()
|
||||
if (!value) return null
|
||||
return value
|
||||
}
|
||||
|
||||
export async function isOutdated(pkg: string, cachedVersion: string, cwd?: string): Promise<boolean> {
|
||||
const latestVersion = await info(pkg, "version", cwd)
|
||||
if (!latestVersion) {
|
||||
log.warn("Failed to resolve latest version, using cached", { pkg, cachedVersion })
|
||||
return false
|
||||
}
|
||||
|
||||
const isRange = /[\s^~*xX<>|=]/.test(cachedVersion)
|
||||
if (isRange) return !semver.satisfies(latestVersion, cachedVersion)
|
||||
|
||||
return semver.lt(cachedVersion, latestVersion)
|
||||
}
|
||||
}
|
||||
@@ -51,8 +51,8 @@ export namespace Bus {
|
||||
})
|
||||
const pending = []
|
||||
for (const key of [def.type, "*"]) {
|
||||
const match = [...(state().subscriptions.get(key) ?? [])]
|
||||
for (const sub of match) {
|
||||
const match = state().subscriptions.get(key)
|
||||
for (const sub of match ?? []) {
|
||||
pending.push(sub(payload))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,6 +349,7 @@ export const ProvidersLoginCommand = cmd({
|
||||
value: x.id,
|
||||
hint: {
|
||||
opencode: "recommended",
|
||||
anthropic: "API key",
|
||||
openai: "ChatGPT Plus/Pro or API key",
|
||||
}[x.id],
|
||||
})),
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useToast } from "../ui/toast"
|
||||
import { useKeybind } from "../context/keybind"
|
||||
import { DialogSessionList } from "./workspace/dialog-session-list"
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk/v2"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
|
||||
async function openWorkspace(input: {
|
||||
dialog: ReturnType<typeof useDialog>
|
||||
@@ -57,7 +56,7 @@ async function openWorkspace(input: {
|
||||
return
|
||||
}
|
||||
if (result.response.status >= 500 && result.response.status < 600) {
|
||||
await sleep(1000)
|
||||
await Bun.sleep(1000)
|
||||
continue
|
||||
}
|
||||
if (!result.data) {
|
||||
|
||||
@@ -13,7 +13,6 @@ import { MessageID, PartID } from "@/session/schema"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
import { useKeybind } from "@tui/context/keybind"
|
||||
import { usePromptHistory, type PromptInfo } from "./history"
|
||||
import { assign } from "./part"
|
||||
import { usePromptStash } from "./stash"
|
||||
import { DialogStash } from "../dialog-stash"
|
||||
import { type AutocompleteRef, Autocomplete } from "./autocomplete"
|
||||
@@ -644,7 +643,10 @@ export function Prompt(props: PromptProps) {
|
||||
type: "text",
|
||||
text: inputText,
|
||||
},
|
||||
...nonTextParts.map(assign),
|
||||
...nonTextParts.map((x) => ({
|
||||
id: PartID.ascending(),
|
||||
...x,
|
||||
})),
|
||||
],
|
||||
})
|
||||
.catch(() => {})
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { PartID } from "@/session/schema"
|
||||
import type { PromptInfo } from "./history"
|
||||
|
||||
type Item = PromptInfo["parts"][number]
|
||||
|
||||
export function strip(part: Item & { id: string; messageID: string; sessionID: string }): Item {
|
||||
const { id: _id, messageID: _messageID, sessionID: _sessionID, ...rest } = part
|
||||
return rest
|
||||
}
|
||||
|
||||
export function assign(part: Item): Item & { id: PartID } {
|
||||
return {
|
||||
...part,
|
||||
id: PartID.ascending(),
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import { useSDK } from "@tui/context/sdk"
|
||||
import { useRoute } from "@tui/context/route"
|
||||
import { useDialog } from "../../ui/dialog"
|
||||
import type { PromptInfo } from "@tui/component/prompt/history"
|
||||
import { strip } from "@tui/component/prompt/part"
|
||||
|
||||
export function DialogForkFromTimeline(props: { sessionID: string; onMove: (messageID: string) => void }) {
|
||||
const sync = useSync()
|
||||
@@ -43,7 +42,7 @@ export function DialogForkFromTimeline(props: { sessionID: string; onMove: (mess
|
||||
if (part.type === "text") {
|
||||
if (!part.synthetic) agg.input += part.text
|
||||
}
|
||||
if (part.type === "file") agg.parts.push(strip(part))
|
||||
if (part.type === "file") agg.parts.push(part)
|
||||
return agg
|
||||
},
|
||||
{ input: "", parts: [] as PromptInfo["parts"] },
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useSDK } from "@tui/context/sdk"
|
||||
import { useRoute } from "@tui/context/route"
|
||||
import { Clipboard } from "@tui/util/clipboard"
|
||||
import type { PromptInfo } from "@tui/component/prompt/history"
|
||||
import { strip } from "@tui/component/prompt/part"
|
||||
|
||||
export function DialogMessage(props: {
|
||||
messageID: string
|
||||
@@ -41,7 +40,7 @@ export function DialogMessage(props: {
|
||||
if (part.type === "text") {
|
||||
if (!part.synthetic) agg.input += part.text
|
||||
}
|
||||
if (part.type === "file") agg.parts.push(strip(part))
|
||||
if (part.type === "file") agg.parts.push(part)
|
||||
return agg
|
||||
},
|
||||
{ input: "", parts: [] as PromptInfo["parts"] },
|
||||
|
||||
@@ -907,12 +907,12 @@ export function Session() {
|
||||
const filename = options.filename.trim()
|
||||
const filepath = path.join(exportDir, filename)
|
||||
|
||||
await Filesystem.write(filepath, transcript)
|
||||
await Bun.write(filepath, transcript)
|
||||
|
||||
// Open with EDITOR if available
|
||||
const result = await Editor.open({ value: transcript, renderer })
|
||||
if (result !== undefined) {
|
||||
await Filesystem.write(filepath, result)
|
||||
await Bun.write(filepath, result)
|
||||
}
|
||||
|
||||
toast.show({ message: `Session exported to ${filename}`, variant: "success" })
|
||||
|
||||
@@ -8,6 +8,7 @@ import { upgrade } from "@/cli/upgrade"
|
||||
import { Config } from "@/config/config"
|
||||
import { GlobalBus } from "@/bus/global"
|
||||
import { createOpencodeClient, type Event } from "@opencode-ai/sdk/v2"
|
||||
import type { BunWebSocketData } from "hono/bun"
|
||||
import { Flag } from "@/flag/flag"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
|
||||
@@ -37,7 +38,7 @@ GlobalBus.on("event", (event) => {
|
||||
Rpc.emit("global.event", event)
|
||||
})
|
||||
|
||||
let server: Awaited<ReturnType<typeof Server.listen>> | undefined
|
||||
let server: Bun.Server<BunWebSocketData> | undefined
|
||||
|
||||
const eventStream = {
|
||||
abort: undefined as AbortController | undefined,
|
||||
@@ -119,7 +120,7 @@ export const rpc = {
|
||||
},
|
||||
async server(input: { port: number; hostname: string; mdns?: boolean; cors?: string[] }) {
|
||||
if (server) await server.stop(true)
|
||||
server = await Server.listen(input)
|
||||
server = Server.listen(input)
|
||||
return { url: server.url.toString() }
|
||||
},
|
||||
async checkUpgrade(input: { directory: string }) {
|
||||
@@ -142,7 +143,7 @@ export const rpc = {
|
||||
Log.Default.info("worker shutting down")
|
||||
if (eventStream.abort) eventStream.abort.abort()
|
||||
await Instance.disposeAll()
|
||||
if (server) await server.stop(true)
|
||||
if (server) server.stop(true)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Log } from "../util/log"
|
||||
import path from "path"
|
||||
import { pathToFileURL } from "url"
|
||||
import { pathToFileURL, fileURLToPath } from "url"
|
||||
import { createRequire } from "module"
|
||||
import os from "os"
|
||||
import z from "zod"
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
} from "jsonc-parser"
|
||||
import { Instance } from "../project/instance"
|
||||
import { LSPServer } from "../lsp/server"
|
||||
import { BunProc } from "@/bun"
|
||||
import { Installation } from "@/installation"
|
||||
import { ConfigMarkdown } from "./markdown"
|
||||
import { constants, existsSync } from "fs"
|
||||
@@ -29,11 +30,14 @@ import { Bus } from "@/bus"
|
||||
import { GlobalBus } from "@/bus/global"
|
||||
import { Event } from "../server/event"
|
||||
import { Glob } from "../util/glob"
|
||||
import { PackageRegistry } from "@/bun/registry"
|
||||
import { proxied } from "@/util/proxied"
|
||||
import { iife } from "@/util/iife"
|
||||
import { Account } from "@/account"
|
||||
import { ConfigPaths } from "./paths"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { Npm } from "@/npm"
|
||||
import { Process } from "@/util/process"
|
||||
import { Lock } from "@/util/lock"
|
||||
|
||||
export namespace Config {
|
||||
const ModelId = z.string().meta({ $ref: "https://models.dev/model-schema.json#/$defs/Model" })
|
||||
@@ -150,7 +154,8 @@ export namespace Config {
|
||||
|
||||
deps.push(
|
||||
iife(async () => {
|
||||
await installDependencies(dir)
|
||||
const shouldInstall = await needsInstall(dir)
|
||||
if (shouldInstall) await installDependencies(dir)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -266,10 +271,6 @@ export namespace Config {
|
||||
}
|
||||
|
||||
export async function installDependencies(dir: string) {
|
||||
if (!(await isWritable(dir))) {
|
||||
log.info("config dir is not writable, skipping dependency install", { dir })
|
||||
return
|
||||
}
|
||||
const pkg = path.join(dir, "package.json")
|
||||
const targetVersion = Installation.isLocal() ? "*" : Installation.VERSION
|
||||
|
||||
@@ -283,15 +284,43 @@ export namespace Config {
|
||||
await Filesystem.writeJson(pkg, json)
|
||||
|
||||
const gitignore = path.join(dir, ".gitignore")
|
||||
if (!(await Filesystem.exists(gitignore)))
|
||||
await Filesystem.write(
|
||||
gitignore,
|
||||
["node_modules", "plans", "package.json", "bun.lock", ".gitignore", "package-lock.json"].join("\n"),
|
||||
)
|
||||
const hasGitIgnore = await Filesystem.exists(gitignore)
|
||||
if (!hasGitIgnore)
|
||||
await Filesystem.write(gitignore, ["node_modules", "package.json", "bun.lock", ".gitignore"].join("\n"))
|
||||
|
||||
// Install any additional dependencies defined in the package.json
|
||||
// This allows local plugins and custom tools to use external packages
|
||||
await Npm.install(dir)
|
||||
using _ = await Lock.write("bun-install")
|
||||
await BunProc.run(
|
||||
[
|
||||
"install",
|
||||
// TODO: get rid of this case (see: https://github.com/oven-sh/bun/issues/19936)
|
||||
...(proxied() || process.env.CI ? ["--no-cache"] : []),
|
||||
],
|
||||
{ cwd: dir },
|
||||
).catch((err) => {
|
||||
if (err instanceof Process.RunFailedError) {
|
||||
const detail = {
|
||||
dir,
|
||||
cmd: err.cmd,
|
||||
code: err.code,
|
||||
stdout: err.stdout.toString(),
|
||||
stderr: err.stderr.toString(),
|
||||
}
|
||||
if (Flag.OPENCODE_STRICT_CONFIG_DEPS) {
|
||||
log.error("failed to install dependencies", detail)
|
||||
throw err
|
||||
}
|
||||
log.warn("failed to install dependencies", detail)
|
||||
return
|
||||
}
|
||||
|
||||
if (Flag.OPENCODE_STRICT_CONFIG_DEPS) {
|
||||
log.error("failed to install dependencies", { dir, error: err })
|
||||
throw err
|
||||
}
|
||||
log.warn("failed to install dependencies", { dir, error: err })
|
||||
})
|
||||
}
|
||||
|
||||
async function isWritable(dir: string) {
|
||||
@@ -303,6 +332,41 @@ export namespace Config {
|
||||
}
|
||||
}
|
||||
|
||||
export async function needsInstall(dir: string) {
|
||||
// Some config dirs may be read-only.
|
||||
// Installing deps there will fail; skip installation in that case.
|
||||
const writable = await isWritable(dir)
|
||||
if (!writable) {
|
||||
log.debug("config dir is not writable, skipping dependency install", { dir })
|
||||
return false
|
||||
}
|
||||
|
||||
const nodeModules = path.join(dir, "node_modules")
|
||||
if (!existsSync(nodeModules)) return true
|
||||
|
||||
const pkg = path.join(dir, "package.json")
|
||||
const pkgExists = await Filesystem.exists(pkg)
|
||||
if (!pkgExists) return true
|
||||
|
||||
const parsed = await Filesystem.readJson<{ dependencies?: Record<string, string> }>(pkg).catch(() => null)
|
||||
const dependencies = parsed?.dependencies ?? {}
|
||||
const depVersion = dependencies["@opencode-ai/plugin"]
|
||||
if (!depVersion) return true
|
||||
|
||||
const targetVersion = Installation.isLocal() ? "latest" : Installation.VERSION
|
||||
if (targetVersion === "latest") {
|
||||
const isOutdated = await PackageRegistry.isOutdated("@opencode-ai/plugin", depVersion, dir)
|
||||
if (!isOutdated) return false
|
||||
log.info("Cached version is outdated, proceeding with install", {
|
||||
pkg: "@opencode-ai/plugin",
|
||||
cachedVersion: depVersion,
|
||||
})
|
||||
return true
|
||||
}
|
||||
if (depVersion === targetVersion) return false
|
||||
return true
|
||||
}
|
||||
|
||||
function rel(item: string, patterns: string[]) {
|
||||
const normalizedItem = item.replaceAll("\\", "/")
|
||||
for (const pattern of patterns) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import z from "zod"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
import { fn } from "@/util/fn"
|
||||
import { Database, eq } from "@/storage/db"
|
||||
import { Project } from "@/project/project"
|
||||
@@ -118,7 +117,7 @@ export namespace Workspace {
|
||||
const adaptor = await getAdaptor(space.type)
|
||||
const res = await adaptor.fetch(space, "/event", { method: "GET", signal: stop }).catch(() => undefined)
|
||||
if (!res || !res.ok || !res.body) {
|
||||
await sleep(1000)
|
||||
await Bun.sleep(1000)
|
||||
continue
|
||||
}
|
||||
await parseSSE(res.body, stop, (event) => {
|
||||
@@ -128,7 +127,7 @@ export namespace Workspace {
|
||||
})
|
||||
})
|
||||
// Wait 250ms and retry if SSE connection fails
|
||||
await sleep(250)
|
||||
await Bun.sleep(250)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,40 +1,43 @@
|
||||
import { text } from "node:stream/consumers"
|
||||
import { BunProc } from "../bun"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { Process } from "../util/process"
|
||||
import { which } from "../util/which"
|
||||
import { Flag } from "@/flag/flag"
|
||||
import { Npm } from "@/npm"
|
||||
|
||||
export interface Info {
|
||||
name: string
|
||||
command: string[]
|
||||
environment?: Record<string, string>
|
||||
extensions: string[]
|
||||
enabled(): Promise<string[] | false>
|
||||
enabled(): Promise<boolean>
|
||||
}
|
||||
|
||||
export const gofmt: Info = {
|
||||
name: "gofmt",
|
||||
command: ["gofmt", "-w", "$FILE"],
|
||||
extensions: [".go"],
|
||||
async enabled() {
|
||||
const p = which("gofmt")
|
||||
if (p === null) return false
|
||||
return [p, "-w", "$FILE"]
|
||||
return which("gofmt") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const mix: Info = {
|
||||
name: "mix",
|
||||
command: ["mix", "format", "$FILE"],
|
||||
extensions: [".ex", ".exs", ".eex", ".heex", ".leex", ".neex", ".sface"],
|
||||
async enabled() {
|
||||
const p = which("mix")
|
||||
if (p === null) return false
|
||||
return [p, "format", "$FILE"]
|
||||
return which("mix") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const prettier: Info = {
|
||||
name: "prettier",
|
||||
command: [BunProc.which(), "x", "prettier", "--write", "$FILE"],
|
||||
environment: {
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
extensions: [
|
||||
".js",
|
||||
".jsx",
|
||||
@@ -70,9 +73,8 @@ export const prettier: Info = {
|
||||
dependencies?: Record<string, string>
|
||||
devDependencies?: Record<string, string>
|
||||
}>(item)
|
||||
if (json.dependencies?.prettier || json.devDependencies?.prettier) {
|
||||
return [await Npm.which("prettier"), "--write", "$FILE"]
|
||||
}
|
||||
if (json.dependencies?.prettier) return true
|
||||
if (json.devDependencies?.prettier) return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
@@ -80,6 +82,10 @@ export const prettier: Info = {
|
||||
|
||||
export const oxfmt: Info = {
|
||||
name: "oxfmt",
|
||||
command: [BunProc.which(), "x", "oxfmt", "$FILE"],
|
||||
environment: {
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
extensions: [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"],
|
||||
async enabled() {
|
||||
if (!Flag.OPENCODE_EXPERIMENTAL_OXFMT) return false
|
||||
@@ -89,9 +95,8 @@ export const oxfmt: Info = {
|
||||
dependencies?: Record<string, string>
|
||||
devDependencies?: Record<string, string>
|
||||
}>(item)
|
||||
if (json.dependencies?.oxfmt || json.devDependencies?.oxfmt) {
|
||||
return [await Npm.which("oxfmt"), "$FILE"]
|
||||
}
|
||||
if (json.dependencies?.oxfmt) return true
|
||||
if (json.devDependencies?.oxfmt) return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
@@ -99,6 +104,10 @@ export const oxfmt: Info = {
|
||||
|
||||
export const biome: Info = {
|
||||
name: "biome",
|
||||
command: [BunProc.which(), "x", "@biomejs/biome", "check", "--write", "$FILE"],
|
||||
environment: {
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
extensions: [
|
||||
".js",
|
||||
".jsx",
|
||||
@@ -132,7 +141,7 @@ export const biome: Info = {
|
||||
for (const config of configs) {
|
||||
const found = await Filesystem.findUp(config, Instance.directory, Instance.worktree)
|
||||
if (found.length > 0) {
|
||||
return [await Npm.which("@biomejs/biome"), "check", "--write", "$FILE"]
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -141,49 +150,47 @@ export const biome: Info = {
|
||||
|
||||
export const zig: Info = {
|
||||
name: "zig",
|
||||
command: ["zig", "fmt", "$FILE"],
|
||||
extensions: [".zig", ".zon"],
|
||||
async enabled() {
|
||||
const p = which("zig")
|
||||
if (p === null) return false
|
||||
return [p, "fmt", "$FILE"]
|
||||
return which("zig") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const clang: Info = {
|
||||
name: "clang-format",
|
||||
command: ["clang-format", "-i", "$FILE"],
|
||||
extensions: [".c", ".cc", ".cpp", ".cxx", ".c++", ".h", ".hh", ".hpp", ".hxx", ".h++", ".ino", ".C", ".H"],
|
||||
async enabled() {
|
||||
const items = await Filesystem.findUp(".clang-format", Instance.directory, Instance.worktree)
|
||||
if (items.length === 0) return false
|
||||
return ["clang-format", "-i", "$FILE"]
|
||||
return items.length > 0
|
||||
},
|
||||
}
|
||||
|
||||
export const ktlint: Info = {
|
||||
name: "ktlint",
|
||||
command: ["ktlint", "-F", "$FILE"],
|
||||
extensions: [".kt", ".kts"],
|
||||
async enabled() {
|
||||
const p = which("ktlint")
|
||||
if (p === null) return false
|
||||
return [p, "-F", "$FILE"]
|
||||
return which("ktlint") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const ruff: Info = {
|
||||
name: "ruff",
|
||||
command: ["ruff", "format", "$FILE"],
|
||||
extensions: [".py", ".pyi"],
|
||||
async enabled() {
|
||||
const p = which("ruff")
|
||||
if (p === null) return false
|
||||
if (!which("ruff")) return false
|
||||
const configs = ["pyproject.toml", "ruff.toml", ".ruff.toml"]
|
||||
for (const config of configs) {
|
||||
const found = await Filesystem.findUp(config, Instance.directory, Instance.worktree)
|
||||
if (found.length > 0) {
|
||||
if (config === "pyproject.toml") {
|
||||
const content = await Filesystem.readText(found[0])
|
||||
if (content.includes("[tool.ruff]")) return [p, "format", "$FILE"]
|
||||
if (content.includes("[tool.ruff]")) return true
|
||||
} else {
|
||||
return [p, "format", "$FILE"]
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,7 +199,7 @@ export const ruff: Info = {
|
||||
const found = await Filesystem.findUp(dep, Instance.directory, Instance.worktree)
|
||||
if (found.length > 0) {
|
||||
const content = await Filesystem.readText(found[0])
|
||||
if (content.includes("ruff")) return [p, "format", "$FILE"]
|
||||
if (content.includes("ruff")) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -201,13 +208,14 @@ export const ruff: Info = {
|
||||
|
||||
export const rlang: Info = {
|
||||
name: "air",
|
||||
command: ["air", "format", "$FILE"],
|
||||
extensions: [".R"],
|
||||
async enabled() {
|
||||
const airPath = which("air")
|
||||
if (airPath == null) return false
|
||||
|
||||
try {
|
||||
const proc = Process.spawn([airPath, "--help"], {
|
||||
const proc = Process.spawn(["air", "--help"], {
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
})
|
||||
@@ -219,10 +227,7 @@ export const rlang: Info = {
|
||||
const firstLine = output.split("\n")[0]
|
||||
const hasR = firstLine.includes("R language")
|
||||
const hasFormatter = firstLine.includes("formatter")
|
||||
if (hasR && hasFormatter) {
|
||||
return [airPath, "format", "$FILE"]
|
||||
}
|
||||
return false
|
||||
return hasR && hasFormatter
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
@@ -231,14 +236,14 @@ export const rlang: Info = {
|
||||
|
||||
export const uvformat: Info = {
|
||||
name: "uv",
|
||||
command: ["uv", "format", "--", "$FILE"],
|
||||
extensions: [".py", ".pyi"],
|
||||
async enabled() {
|
||||
if (await ruff.enabled()) return false
|
||||
const uvPath = which("uv")
|
||||
if (uvPath !== null) {
|
||||
const proc = Process.spawn([uvPath, "format", "--help"], { stderr: "pipe", stdout: "pipe" })
|
||||
if (which("uv") !== null) {
|
||||
const proc = Process.spawn(["uv", "format", "--help"], { stderr: "pipe", stdout: "pipe" })
|
||||
const code = await proc.exited
|
||||
if (code === 0) return [uvPath, "format", "--", "$FILE"]
|
||||
return code === 0
|
||||
}
|
||||
return false
|
||||
},
|
||||
@@ -246,118 +251,108 @@ export const uvformat: Info = {
|
||||
|
||||
export const rubocop: Info = {
|
||||
name: "rubocop",
|
||||
command: ["rubocop", "--autocorrect", "$FILE"],
|
||||
extensions: [".rb", ".rake", ".gemspec", ".ru"],
|
||||
async enabled() {
|
||||
const path = which("rubocop")
|
||||
if (path === null) return false
|
||||
return [path, "--autocorrect", "$FILE"]
|
||||
return which("rubocop") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const standardrb: Info = {
|
||||
name: "standardrb",
|
||||
command: ["standardrb", "--fix", "$FILE"],
|
||||
extensions: [".rb", ".rake", ".gemspec", ".ru"],
|
||||
async enabled() {
|
||||
const path = which("standardrb")
|
||||
if (path === null) return false
|
||||
return [path, "--fix", "$FILE"]
|
||||
return which("standardrb") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const htmlbeautifier: Info = {
|
||||
name: "htmlbeautifier",
|
||||
command: ["htmlbeautifier", "$FILE"],
|
||||
extensions: [".erb", ".html.erb"],
|
||||
async enabled() {
|
||||
const path = which("htmlbeautifier")
|
||||
if (path === null) return false
|
||||
return [path, "$FILE"]
|
||||
return which("htmlbeautifier") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const dart: Info = {
|
||||
name: "dart",
|
||||
command: ["dart", "format", "$FILE"],
|
||||
extensions: [".dart"],
|
||||
async enabled() {
|
||||
const path = which("dart")
|
||||
if (path === null) return false
|
||||
return [path, "format", "$FILE"]
|
||||
return which("dart") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const ocamlformat: Info = {
|
||||
name: "ocamlformat",
|
||||
command: ["ocamlformat", "-i", "$FILE"],
|
||||
extensions: [".ml", ".mli"],
|
||||
async enabled() {
|
||||
const path = which("ocamlformat")
|
||||
if (!path) return false
|
||||
if (!which("ocamlformat")) return false
|
||||
const items = await Filesystem.findUp(".ocamlformat", Instance.directory, Instance.worktree)
|
||||
if (items.length === 0) return false
|
||||
return [path, "-i", "$FILE"]
|
||||
return items.length > 0
|
||||
},
|
||||
}
|
||||
|
||||
export const terraform: Info = {
|
||||
name: "terraform",
|
||||
command: ["terraform", "fmt", "$FILE"],
|
||||
extensions: [".tf", ".tfvars"],
|
||||
async enabled() {
|
||||
const path = which("terraform")
|
||||
if (path === null) return false
|
||||
return [path, "fmt", "$FILE"]
|
||||
return which("terraform") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const latexindent: Info = {
|
||||
name: "latexindent",
|
||||
command: ["latexindent", "-w", "-s", "$FILE"],
|
||||
extensions: [".tex"],
|
||||
async enabled() {
|
||||
const path = which("latexindent")
|
||||
if (path === null) return false
|
||||
return [path, "-w", "-s", "$FILE"]
|
||||
return which("latexindent") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const gleam: Info = {
|
||||
name: "gleam",
|
||||
command: ["gleam", "format", "$FILE"],
|
||||
extensions: [".gleam"],
|
||||
async enabled() {
|
||||
const path = which("gleam")
|
||||
if (path === null) return false
|
||||
return [path, "format", "$FILE"]
|
||||
return which("gleam") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const shfmt: Info = {
|
||||
name: "shfmt",
|
||||
command: ["shfmt", "-w", "$FILE"],
|
||||
extensions: [".sh", ".bash"],
|
||||
async enabled() {
|
||||
const path = which("shfmt")
|
||||
if (path === null) return false
|
||||
return [path, "-w", "$FILE"]
|
||||
return which("shfmt") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const nixfmt: Info = {
|
||||
name: "nixfmt",
|
||||
command: ["nixfmt", "$FILE"],
|
||||
extensions: [".nix"],
|
||||
async enabled() {
|
||||
const path = which("nixfmt")
|
||||
if (path === null) return false
|
||||
return [path, "$FILE"]
|
||||
return which("nixfmt") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const rustfmt: Info = {
|
||||
name: "rustfmt",
|
||||
command: ["rustfmt", "$FILE"],
|
||||
extensions: [".rs"],
|
||||
async enabled() {
|
||||
const path = which("rustfmt")
|
||||
if (path === null) return false
|
||||
return [path, "$FILE"]
|
||||
return which("rustfmt") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const pint: Info = {
|
||||
name: "pint",
|
||||
command: ["./vendor/bin/pint", "$FILE"],
|
||||
extensions: [".php"],
|
||||
async enabled() {
|
||||
const items = await Filesystem.findUp("composer.json", Instance.directory, Instance.worktree)
|
||||
@@ -366,9 +361,8 @@ export const pint: Info = {
|
||||
require?: Record<string, string>
|
||||
"require-dev"?: Record<string, string>
|
||||
}>(item)
|
||||
if (json.require?.["laravel/pint"] || json["require-dev"]?.["laravel/pint"]) {
|
||||
return ["./vendor/bin/pint", "$FILE"]
|
||||
}
|
||||
if (json.require?.["laravel/pint"]) return true
|
||||
if (json["require-dev"]?.["laravel/pint"]) return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
@@ -376,30 +370,27 @@ export const pint: Info = {
|
||||
|
||||
export const ormolu: Info = {
|
||||
name: "ormolu",
|
||||
command: ["ormolu", "-i", "$FILE"],
|
||||
extensions: [".hs"],
|
||||
async enabled() {
|
||||
const path = which("ormolu")
|
||||
if (path === null) return false
|
||||
return [path, "-i", "$FILE"]
|
||||
return which("ormolu") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const cljfmt: Info = {
|
||||
name: "cljfmt",
|
||||
command: ["cljfmt", "fix", "--quiet", "$FILE"],
|
||||
extensions: [".clj", ".cljs", ".cljc", ".edn"],
|
||||
async enabled() {
|
||||
const path = which("cljfmt")
|
||||
if (path === null) return false
|
||||
return [path, "fix", "--quiet", "$FILE"]
|
||||
return which("cljfmt") !== null
|
||||
},
|
||||
}
|
||||
|
||||
export const dfmt: Info = {
|
||||
name: "dfmt",
|
||||
command: ["dfmt", "-i", "$FILE"],
|
||||
extensions: [".d"],
|
||||
async enabled() {
|
||||
const path = which("dfmt")
|
||||
if (path === null) return false
|
||||
return [path, "-i", "$FILE"]
|
||||
return which("dfmt") !== null
|
||||
},
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export namespace Format {
|
||||
Effect.gen(function* () {
|
||||
const instance = yield* InstanceContext
|
||||
|
||||
const enabled: Record<string, string[] | false> = {}
|
||||
const enabled: Record<string, boolean> = {}
|
||||
const formatters: Record<string, Formatter.Info> = {}
|
||||
|
||||
const cfg = yield* Effect.promise(() => Config.get())
|
||||
@@ -62,7 +62,7 @@ export namespace Format {
|
||||
formatters[name] = {
|
||||
...info,
|
||||
name,
|
||||
enabled: async (): Promise<string[] | false> => info.command,
|
||||
enabled: async () => true,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -79,22 +79,13 @@ export namespace Format {
|
||||
}
|
||||
|
||||
async function getFormatter(ext: string) {
|
||||
const result: Array<{
|
||||
name: string
|
||||
command: string[]
|
||||
environment?: Record<string, string>
|
||||
}> = []
|
||||
const result = []
|
||||
for (const item of Object.values(formatters)) {
|
||||
log.info("checking", { name: item.name, ext })
|
||||
if (!item.extensions.includes(ext)) continue
|
||||
const cmd = await isEnabled(item)
|
||||
if (!cmd) continue
|
||||
if (!(await isEnabled(item))) continue
|
||||
log.info("enabled", { name: item.name, ext })
|
||||
result.push({
|
||||
name: item.name,
|
||||
command: cmd,
|
||||
environment: item.environment,
|
||||
})
|
||||
result.push(item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -150,7 +141,7 @@ export namespace Format {
|
||||
result.push({
|
||||
name: formatter.name,
|
||||
extensions: formatter.extensions,
|
||||
enabled: !!isOn,
|
||||
enabled: isOn,
|
||||
})
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from "path"
|
||||
import os from "os"
|
||||
import { Global } from "../global"
|
||||
import { Log } from "../util/log"
|
||||
import { BunProc } from "../bun"
|
||||
import { text } from "node:stream/consumers"
|
||||
import fs from "fs/promises"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
@@ -13,7 +14,6 @@ import { Process } from "../util/process"
|
||||
import { which } from "../util/which"
|
||||
import { Module } from "@opencode-ai/util/module"
|
||||
import { spawn } from "./launch"
|
||||
import { Npm } from "@/npm"
|
||||
|
||||
export namespace LSPServer {
|
||||
const log = Log.create({ service: "lsp.server" })
|
||||
@@ -103,10 +103,11 @@ export namespace LSPServer {
|
||||
const tsserver = Module.resolve("typescript/lib/tsserver.js", Instance.directory)
|
||||
log.info("typescript server", { tsserver })
|
||||
if (!tsserver) return
|
||||
const proc = spawn(await Npm.which("typescript-language-server"), ["--stdio"], {
|
||||
const proc = spawn(BunProc.which(), ["x", "typescript-language-server", "--stdio"], {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
})
|
||||
return {
|
||||
@@ -128,14 +129,36 @@ export namespace LSPServer {
|
||||
let binary = which("vue-language-server")
|
||||
const args: string[] = []
|
||||
if (!binary) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
binary = await Npm.which("@vue/language-server")
|
||||
const js = path.join(
|
||||
Global.Path.bin,
|
||||
"node_modules",
|
||||
"@vue",
|
||||
"language-server",
|
||||
"bin",
|
||||
"vue-language-server.js",
|
||||
)
|
||||
if (!(await Filesystem.exists(js))) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
await Process.spawn([BunProc.which(), "install", "@vue/language-server"], {
|
||||
cwd: Global.Path.bin,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "pipe",
|
||||
}).exited
|
||||
}
|
||||
binary = BunProc.which()
|
||||
args.push("run", js)
|
||||
}
|
||||
args.push("--stdio")
|
||||
const proc = spawn(binary, args, {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
})
|
||||
return {
|
||||
@@ -191,10 +214,11 @@ export namespace LSPServer {
|
||||
log.info("installed VS Code ESLint server", { serverPath })
|
||||
}
|
||||
|
||||
const proc = spawn("node", [serverPath, "--stdio"], {
|
||||
const proc = spawn(BunProc.which(), [serverPath, "--stdio"], {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
})
|
||||
|
||||
@@ -321,14 +345,15 @@ export namespace LSPServer {
|
||||
if (!bin) {
|
||||
const resolved = Module.resolve("biome", root)
|
||||
if (!resolved) return
|
||||
bin = await Npm.which("biome")
|
||||
args = ["lsp-proxy", "--stdio"]
|
||||
bin = BunProc.which()
|
||||
args = ["x", "biome", "lsp-proxy", "--stdio"]
|
||||
}
|
||||
|
||||
const proc = spawn(bin, args, {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
})
|
||||
|
||||
@@ -347,7 +372,9 @@ export namespace LSPServer {
|
||||
},
|
||||
extensions: [".go"],
|
||||
async spawn(root) {
|
||||
let bin = which("gopls")
|
||||
let bin = which("gopls", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
if (!bin) {
|
||||
if (!which("go")) return
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
@@ -382,7 +409,9 @@ export namespace LSPServer {
|
||||
root: NearestRoot(["Gemfile"]),
|
||||
extensions: [".rb", ".rake", ".gemspec", ".ru"],
|
||||
async spawn(root) {
|
||||
let bin = which("rubocop")
|
||||
let bin = which("rubocop", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
if (!bin) {
|
||||
const ruby = which("ruby")
|
||||
const gem = which("gem")
|
||||
@@ -487,8 +516,19 @@ export namespace LSPServer {
|
||||
let binary = which("pyright-langserver")
|
||||
const args = []
|
||||
if (!binary) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
binary = await Npm.which("pyright")
|
||||
const js = path.join(Global.Path.bin, "node_modules", "pyright", "dist", "pyright-langserver.js")
|
||||
if (!(await Filesystem.exists(js))) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
await Process.spawn([BunProc.which(), "install", "pyright"], {
|
||||
cwd: Global.Path.bin,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
}).exited
|
||||
}
|
||||
binary = BunProc.which()
|
||||
args.push(...["run", js])
|
||||
}
|
||||
args.push("--stdio")
|
||||
|
||||
@@ -512,6 +552,7 @@ export namespace LSPServer {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
})
|
||||
return {
|
||||
@@ -589,7 +630,9 @@ export namespace LSPServer {
|
||||
extensions: [".zig", ".zon"],
|
||||
root: NearestRoot(["build.zig"]),
|
||||
async spawn(root) {
|
||||
let bin = which("zls")
|
||||
let bin = which("zls", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
|
||||
if (!bin) {
|
||||
const zig = which("zig")
|
||||
@@ -699,7 +742,9 @@ export namespace LSPServer {
|
||||
root: NearestRoot([".slnx", ".sln", ".csproj", "global.json"]),
|
||||
extensions: [".cs"],
|
||||
async spawn(root) {
|
||||
let bin = which("csharp-ls")
|
||||
let bin = which("csharp-ls", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
if (!bin) {
|
||||
if (!which("dotnet")) {
|
||||
log.error(".NET SDK is required to install csharp-ls")
|
||||
@@ -736,7 +781,9 @@ export namespace LSPServer {
|
||||
root: NearestRoot([".slnx", ".sln", ".fsproj", "global.json"]),
|
||||
extensions: [".fs", ".fsi", ".fsx", ".fsscript"],
|
||||
async spawn(root) {
|
||||
let bin = which("fsautocomplete")
|
||||
let bin = which("fsautocomplete", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
if (!bin) {
|
||||
if (!which("dotnet")) {
|
||||
log.error(".NET SDK is required to install fsautocomplete")
|
||||
@@ -1002,14 +1049,29 @@ export namespace LSPServer {
|
||||
let binary = which("svelteserver")
|
||||
const args: string[] = []
|
||||
if (!binary) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
binary = await Npm.which("svelte-language-server")
|
||||
const js = path.join(Global.Path.bin, "node_modules", "svelte-language-server", "bin", "server.js")
|
||||
if (!(await Filesystem.exists(js))) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
await Process.spawn([BunProc.which(), "install", "svelte-language-server"], {
|
||||
cwd: Global.Path.bin,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "pipe",
|
||||
}).exited
|
||||
}
|
||||
binary = BunProc.which()
|
||||
args.push("run", js)
|
||||
}
|
||||
args.push("--stdio")
|
||||
const proc = spawn(binary, args, {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
})
|
||||
return {
|
||||
@@ -1034,14 +1096,29 @@ export namespace LSPServer {
|
||||
let binary = which("astro-ls")
|
||||
const args: string[] = []
|
||||
if (!binary) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
binary = await Npm.which("@astrojs/language-server")
|
||||
const js = path.join(Global.Path.bin, "node_modules", "@astrojs", "language-server", "bin", "nodeServer.js")
|
||||
if (!(await Filesystem.exists(js))) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
await Process.spawn([BunProc.which(), "install", "@astrojs/language-server"], {
|
||||
cwd: Global.Path.bin,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "pipe",
|
||||
}).exited
|
||||
}
|
||||
binary = BunProc.which()
|
||||
args.push("run", js)
|
||||
}
|
||||
args.push("--stdio")
|
||||
const proc = spawn(binary, args, {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
})
|
||||
return {
|
||||
@@ -1283,14 +1360,38 @@ export namespace LSPServer {
|
||||
let binary = which("yaml-language-server")
|
||||
const args: string[] = []
|
||||
if (!binary) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
binary = await Npm.which("yaml-language-server")
|
||||
const js = path.join(
|
||||
Global.Path.bin,
|
||||
"node_modules",
|
||||
"yaml-language-server",
|
||||
"out",
|
||||
"server",
|
||||
"src",
|
||||
"server.js",
|
||||
)
|
||||
const exists = await Filesystem.exists(js)
|
||||
if (!exists) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
await Process.spawn([BunProc.which(), "install", "yaml-language-server"], {
|
||||
cwd: Global.Path.bin,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "pipe",
|
||||
}).exited
|
||||
}
|
||||
binary = BunProc.which()
|
||||
args.push("run", js)
|
||||
}
|
||||
args.push("--stdio")
|
||||
const proc = spawn(binary, args, {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
})
|
||||
return {
|
||||
@@ -1312,7 +1413,9 @@ export namespace LSPServer {
|
||||
]),
|
||||
extensions: [".lua"],
|
||||
async spawn(root) {
|
||||
let bin = which("lua-language-server")
|
||||
let bin = which("lua-language-server", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
|
||||
if (!bin) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
@@ -1448,14 +1551,29 @@ export namespace LSPServer {
|
||||
let binary = which("intelephense")
|
||||
const args: string[] = []
|
||||
if (!binary) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
binary = await Npm.which("intelephense")
|
||||
const js = path.join(Global.Path.bin, "node_modules", "intelephense", "lib", "intelephense.js")
|
||||
if (!(await Filesystem.exists(js))) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
await Process.spawn([BunProc.which(), "install", "intelephense"], {
|
||||
cwd: Global.Path.bin,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "pipe",
|
||||
}).exited
|
||||
}
|
||||
binary = BunProc.which()
|
||||
args.push("run", js)
|
||||
}
|
||||
args.push("--stdio")
|
||||
const proc = spawn(binary, args, {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
})
|
||||
return {
|
||||
@@ -1530,14 +1648,29 @@ export namespace LSPServer {
|
||||
let binary = which("bash-language-server")
|
||||
const args: string[] = []
|
||||
if (!binary) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
binary = await Npm.which("bash-language-server")
|
||||
const js = path.join(Global.Path.bin, "node_modules", "bash-language-server", "out", "cli.js")
|
||||
if (!(await Filesystem.exists(js))) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
await Process.spawn([BunProc.which(), "install", "bash-language-server"], {
|
||||
cwd: Global.Path.bin,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "pipe",
|
||||
}).exited
|
||||
}
|
||||
binary = BunProc.which()
|
||||
args.push("run", js)
|
||||
}
|
||||
args.push("start")
|
||||
const proc = spawn(binary, args, {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
})
|
||||
return {
|
||||
@@ -1551,7 +1684,9 @@ export namespace LSPServer {
|
||||
extensions: [".tf", ".tfvars"],
|
||||
root: NearestRoot([".terraform.lock.hcl", "terraform.tfstate", "*.tf"]),
|
||||
async spawn(root) {
|
||||
let bin = which("terraform-ls")
|
||||
let bin = which("terraform-ls", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
|
||||
if (!bin) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
@@ -1632,7 +1767,9 @@ export namespace LSPServer {
|
||||
extensions: [".tex", ".bib"],
|
||||
root: NearestRoot([".latexmkrc", "latexmkrc", ".texlabroot", "texlabroot"]),
|
||||
async spawn(root) {
|
||||
let bin = which("texlab")
|
||||
let bin = which("texlab", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
|
||||
if (!bin) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
@@ -1723,14 +1860,29 @@ export namespace LSPServer {
|
||||
let binary = which("docker-langserver")
|
||||
const args: string[] = []
|
||||
if (!binary) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
binary = await Npm.which("dockerfile-language-server-nodejs")
|
||||
const js = path.join(Global.Path.bin, "node_modules", "dockerfile-language-server-nodejs", "lib", "server.js")
|
||||
if (!(await Filesystem.exists(js))) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
await Process.spawn([BunProc.which(), "install", "dockerfile-language-server-nodejs"], {
|
||||
cwd: Global.Path.bin,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "pipe",
|
||||
}).exited
|
||||
}
|
||||
binary = BunProc.which()
|
||||
args.push("run", js)
|
||||
}
|
||||
args.push("--stdio")
|
||||
const proc = spawn(binary, args, {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
BUN_BE_BUN: "1",
|
||||
},
|
||||
})
|
||||
return {
|
||||
@@ -1814,7 +1966,9 @@ export namespace LSPServer {
|
||||
extensions: [".typ", ".typc"],
|
||||
root: NearestRoot(["typst.toml"]),
|
||||
async spawn(root) {
|
||||
let bin = which("tinymist")
|
||||
let bin = which("tinymist", {
|
||||
PATH: process.env["PATH"] + path.delimiter + Global.Path.bin,
|
||||
})
|
||||
|
||||
if (!bin) {
|
||||
if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) return
|
||||
|
||||
@@ -458,8 +458,9 @@ export namespace MCP {
|
||||
cwd,
|
||||
env: {
|
||||
...process.env,
|
||||
...(cmd === "opencode" ? { BUN_BE_BUN: "1" } : {}),
|
||||
...mcp.environment,
|
||||
} as any,
|
||||
},
|
||||
})
|
||||
transport.stderr?.on("data", (chunk: Buffer) => {
|
||||
log.info(`mcp stderr: ${chunk.toString()}`, { key })
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
// Workaround: Bun on Windows does not support the UV_FS_O_FILEMAP flag that
|
||||
// the `tar` package uses for files < 512KB (fs.open returns EINVAL).
|
||||
// tar silently swallows the error and skips writing files, leaving only empty
|
||||
// directories. Setting __FAKE_PLATFORM__ makes tar fall back to the plain 'w'
|
||||
// flag. See tar's get-write-flag.js.
|
||||
// Must be set before @npmcli/arborist is imported since tar caches the flag
|
||||
// at module evaluation time — so we use a dynamic import() below.
|
||||
if (process.platform === "win32") {
|
||||
process.env.__FAKE_PLATFORM__ = "linux"
|
||||
}
|
||||
|
||||
import semver from "semver"
|
||||
import z from "zod"
|
||||
import { NamedError } from "@opencode-ai/util/error"
|
||||
import { Global } from "../global"
|
||||
import { Lock } from "../util/lock"
|
||||
import { Log } from "../util/log"
|
||||
import path from "path"
|
||||
import { readdir, rm } from "fs/promises"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
|
||||
const { Arborist } = await import("@npmcli/arborist")
|
||||
|
||||
export namespace Npm {
|
||||
const log = Log.create({ service: "npm" })
|
||||
|
||||
export const InstallFailedError = NamedError.create(
|
||||
"NpmInstallFailedError",
|
||||
z.object({
|
||||
pkg: z.string(),
|
||||
}),
|
||||
)
|
||||
|
||||
function directory(pkg: string) {
|
||||
return path.join(Global.Path.cache, "packages", pkg)
|
||||
}
|
||||
|
||||
export async function outdated(pkg: string, cachedVersion: string): Promise<boolean> {
|
||||
const response = await fetch(`https://registry.npmjs.org/${pkg}`)
|
||||
if (!response.ok) {
|
||||
log.warn("Failed to resolve latest version, using cached", { pkg, cachedVersion })
|
||||
return false
|
||||
}
|
||||
|
||||
const data = (await response.json()) as { "dist-tags"?: { latest?: string } }
|
||||
const latestVersion = data?.["dist-tags"]?.latest
|
||||
if (!latestVersion) {
|
||||
log.warn("No latest version found, using cached", { pkg, cachedVersion })
|
||||
return false
|
||||
}
|
||||
|
||||
const range = /[\s^~*xX<>|=]/.test(cachedVersion)
|
||||
if (range) return !semver.satisfies(latestVersion, cachedVersion)
|
||||
|
||||
return semver.lt(cachedVersion, latestVersion)
|
||||
}
|
||||
|
||||
export async function add(pkg: string) {
|
||||
using _ = await Lock.write(`npm-install:${pkg}`)
|
||||
log.info("installing package", {
|
||||
pkg,
|
||||
})
|
||||
const dir = directory(pkg)
|
||||
|
||||
const arborist = new Arborist({
|
||||
path: dir,
|
||||
binLinks: true,
|
||||
progress: false,
|
||||
savePrefix: "",
|
||||
})
|
||||
const tree = await arborist.loadVirtual().catch(() => {})
|
||||
if (tree) {
|
||||
const first = tree.edgesOut.values().next().value?.to
|
||||
if (first) {
|
||||
log.info("package already installed", { pkg })
|
||||
return first.path
|
||||
}
|
||||
}
|
||||
|
||||
const result = await arborist
|
||||
.reify({
|
||||
add: [pkg],
|
||||
save: true,
|
||||
saveType: "prod",
|
||||
})
|
||||
.catch((cause) => {
|
||||
throw new InstallFailedError(
|
||||
{ pkg },
|
||||
{
|
||||
cause,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
const first = result.edgesOut.values().next().value?.to
|
||||
if (!first) throw new InstallFailedError({ pkg })
|
||||
return first.path
|
||||
}
|
||||
|
||||
export async function install(dir: string) {
|
||||
using _ = await Lock.write(`npm-install:${dir}`)
|
||||
log.info("checking dependencies", { dir })
|
||||
|
||||
const reify = async () => {
|
||||
const arb = new Arborist({
|
||||
path: dir,
|
||||
binLinks: true,
|
||||
progress: false,
|
||||
savePrefix: "",
|
||||
})
|
||||
await arb.reify().catch(() => {})
|
||||
}
|
||||
|
||||
if (!(await Filesystem.exists(path.join(dir, "node_modules")))) {
|
||||
log.info("node_modules missing, reifying")
|
||||
await reify()
|
||||
return
|
||||
}
|
||||
|
||||
const pkg = await Filesystem.readJson(path.join(dir, "package.json")).catch(() => ({}))
|
||||
const lock = await Filesystem.readJson(path.join(dir, "package-lock.json")).catch(() => ({}))
|
||||
|
||||
const declared = new Set([
|
||||
...Object.keys(pkg.dependencies || {}),
|
||||
...Object.keys(pkg.devDependencies || {}),
|
||||
...Object.keys(pkg.peerDependencies || {}),
|
||||
...Object.keys(pkg.optionalDependencies || {}),
|
||||
])
|
||||
|
||||
const root = lock.packages?.[""] || {}
|
||||
const locked = new Set([
|
||||
...Object.keys(root.dependencies || {}),
|
||||
...Object.keys(root.devDependencies || {}),
|
||||
...Object.keys(root.peerDependencies || {}),
|
||||
...Object.keys(root.optionalDependencies || {}),
|
||||
])
|
||||
|
||||
for (const name of declared) {
|
||||
if (!locked.has(name)) {
|
||||
log.info("dependency not in lock file, reifying", { name })
|
||||
await reify()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.info("dependencies in sync")
|
||||
}
|
||||
|
||||
export async function which(pkg: string) {
|
||||
const dir = directory(pkg)
|
||||
const binDir = path.join(dir, "node_modules", ".bin")
|
||||
|
||||
const pick = async () => {
|
||||
const files = await readdir(binDir).catch(() => [])
|
||||
if (files.length === 0) return undefined
|
||||
if (files.length === 1) return files[0]
|
||||
// Multiple binaries — resolve from package.json bin field like npx does
|
||||
const pkgJson = await Filesystem.readJson<{ bin?: string | Record<string, string> }>(
|
||||
path.join(dir, "node_modules", pkg, "package.json"),
|
||||
).catch(() => undefined)
|
||||
if (pkgJson?.bin) {
|
||||
const bin = pkgJson.bin
|
||||
if (typeof bin === "string") return path.basename(bin)
|
||||
const keys = Object.keys(bin)
|
||||
if (keys.length === 1) return keys[0]
|
||||
const unscoped = pkg.startsWith("@") ? pkg.split("/")[1] : pkg
|
||||
return bin[unscoped] ? unscoped : keys[0]
|
||||
}
|
||||
return files[0]
|
||||
}
|
||||
|
||||
const bin = await pick()
|
||||
if (bin) return path.join(binDir, bin)
|
||||
|
||||
await rm(path.join(dir, "package-lock.json"), { force: true })
|
||||
await add(pkg)
|
||||
const resolved = await pick()
|
||||
if (!resolved) throw new Error(`No binary found for package "${pkg}" after install`)
|
||||
return path.join(binDir, resolved)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Hooks, PluginInput } from "@opencode-ai/plugin"
|
||||
import { Log } from "../util/log"
|
||||
import { Installation } from "../installation"
|
||||
import { Auth, OAUTH_DUMMY_KEY } from "../auth"
|
||||
import { OAUTH_DUMMY_KEY } from "../auth"
|
||||
import os from "os"
|
||||
import { ProviderTransform } from "@/provider/transform"
|
||||
import { ModelID, ProviderID } from "@/provider/schema"
|
||||
|
||||
86
packages/opencode/src/plugin/gitlab.ts
Normal file
86
packages/opencode/src/plugin/gitlab.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import type { Hooks, PluginInput } from "@opencode-ai/plugin"
|
||||
import { discoverWorkflowModels } from "gitlab-ai-provider"
|
||||
import { Log } from "@/util/log"
|
||||
import { ProviderTransform } from "@/provider/transform"
|
||||
import { ModelID, ProviderID } from "@/provider/schema"
|
||||
|
||||
const log = Log.create({ service: "plugin.gitlab" })
|
||||
|
||||
function str(value: unknown, fallback: string) {
|
||||
if (typeof value === "string" && value) return value
|
||||
return fallback
|
||||
}
|
||||
|
||||
export async function GitlabPlugin(input: PluginInput): Promise<Hooks> {
|
||||
return {
|
||||
provider: {
|
||||
id: "gitlab",
|
||||
models: {
|
||||
async reconcile(args) {
|
||||
const url = str(args.options?.instanceUrl, "https://gitlab.com")
|
||||
|
||||
const token = str(args.options?.apiKey, "")
|
||||
if (!token) return
|
||||
const headers = (): Record<string, string> => {
|
||||
if (args.auth?.type === "api") return { "PRIVATE-TOKEN": token }
|
||||
return { Authorization: `Bearer ${token}` }
|
||||
}
|
||||
|
||||
try {
|
||||
log.info("gitlab model discovery starting", { instanceUrl: url })
|
||||
const res = await discoverWorkflowModels(
|
||||
{ instanceUrl: url, getHeaders: headers },
|
||||
{ workingDirectory: input.directory },
|
||||
)
|
||||
|
||||
if (!res.models.length) {
|
||||
log.info("gitlab model discovery skipped: no models found", {
|
||||
project: res.project ? { id: res.project.id, path: res.project.pathWithNamespace } : null,
|
||||
})
|
||||
return
|
||||
}
|
||||
for (const model of res.models) {
|
||||
if (args.models[model.id]) {
|
||||
continue
|
||||
}
|
||||
|
||||
const m = {
|
||||
id: ModelID.make(model.id),
|
||||
providerID: ProviderID.make("gitlab"),
|
||||
name: `Agent Platform (${model.name})`,
|
||||
api: {
|
||||
id: model.id,
|
||||
url,
|
||||
npm: "gitlab-ai-provider",
|
||||
},
|
||||
status: "active" as const,
|
||||
headers: {},
|
||||
options: { workflowRef: model.ref },
|
||||
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
||||
limit: { context: model.context, output: model.output },
|
||||
capabilities: {
|
||||
temperature: false,
|
||||
reasoning: true,
|
||||
attachment: true,
|
||||
toolcall: true,
|
||||
input: { text: true, audio: false, image: true, video: false, pdf: true },
|
||||
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||
interleaved: false,
|
||||
},
|
||||
release_date: "",
|
||||
variants: {} as Record<string, Record<string, any>>,
|
||||
}
|
||||
m.variants = ProviderTransform.variants(m)
|
||||
args.models[model.id] = m
|
||||
}
|
||||
|
||||
return args.models
|
||||
} catch (err) {
|
||||
log.warn("gitlab model discovery failed", { error: err })
|
||||
return
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user