Compare commits

...

54 Commits

Author SHA1 Message Date
Frank
b4c7042c17 wip: vscode extension 2025-07-20 13:27:37 -04:00
Frank
6965787b33 wip: vscode extension 2025-07-20 13:17:51 -04:00
Frank
ce064b8b0e wip: github action 2025-07-20 13:14:14 -04:00
Frank
0fc546fc6b wip: vscode extension 2025-07-20 13:13:18 -04:00
Frank
77ac9e5ec2 wip: github action 2025-07-20 13:13:00 -04:00
Frank
af2c0b3695 wip: github action 2025-07-20 13:07:48 -04:00
Frank
811b22367d wip: github action 2025-07-20 12:41:02 -04:00
Frank
933d50e25a wip: github actions 2025-07-20 12:36:53 -04:00
Frank
800bee2722 wip: vscode extension 2025-07-20 12:00:09 -04:00
Dax Raad
5b4fb96c2e wip: make api logger sort correctly 2025-07-20 11:54:56 -04:00
Frank
1d20bf343d wip: vscode extension 2025-07-20 11:54:30 -04:00
Frank
79d9bf57f7 wip: vscode extension 2025-07-20 11:47:18 -04:00
Frank
7b63db6a13 wip: vscode extension 2025-07-20 11:45:35 -04:00
Frank
0e1565449e wip: vscode extension 2025-07-20 11:33:44 -04:00
GitHub Action
f9a47fe5a3 ignore: update download stats 2025-07-20 2025-07-20 12:04:10 +00:00
adamdotdevin
2bf9d5d4ec wip: file part source in server/api (optional) 2025-07-20 05:39:18 -05:00
adamdotdevin
c18f9ece69 chore: updated tui gitignore 2025-07-20 05:39:18 -05:00
adamdotdevin
4e3c73c4f5 chore: updated stainless script 2025-07-20 05:39:18 -05:00
b0tmtl
8bf2eeccd0 fix(windows): resolve numlock and French keyboard input issues (#1165) 2025-07-20 05:28:15 -05:00
Dax Raad
6232e0fc58 fix bad layout on first render of chat history 2025-07-19 22:38:36 -04:00
Dax Raad
a8b4aed446 fix bash tool rendering 2025-07-19 22:25:15 -04:00
Aiden Cline
03de0c406d fix: title generation for certain providers (#1159) 2025-07-19 20:01:55 -05:00
Aiden Cline
faf8da8743 fix: adjust editor parsing to handle flags like --wait (#1160) 2025-07-19 20:01:25 -05:00
Dax Raad
3386908fd6 ci: ignore 2025-07-19 19:30:12 -04:00
Dax Raad
5a8847952a ci: ignore 2025-07-19 19:29:05 -04:00
Dax Raad
87d21ebf2b Revert "fix: prevent sparse spacing in hyphenated words (#1102)"
This reverts commit 2b44dbdbf1.
2025-07-19 19:25:15 -04:00
Timo Clasen
a524fc545c fix(hooks): prevent session_complete hook from firing on subagent sessions (#1149) 2025-07-19 18:20:07 -05:00
Dax Raad
4316edaf43 fix first run github copilot 2025-07-19 19:19:38 -04:00
Dax Raad
d845924e8b ci: ignore 2025-07-19 19:00:17 -04:00
Dax Raad
a29b322bdd ci: ignore 2025-07-19 18:54:46 -04:00
Dax Raad
9723ffa7a6 ignore: ci 2025-07-19 18:48:43 -04:00
Dax Raad
f06cd88773 perf: more performance improvements 2025-07-19 18:41:21 -04:00
Dax Raad
9af92b6914 perf: scroll to bottom in thread 2025-07-19 17:55:01 -04:00
Dax Raad
8f64c4b312 disable todo tools when running as task 2025-07-19 15:54:11 -04:00
Dax Raad
a32877e908 ignore: create memo abstraction 2025-07-19 15:26:26 -04:00
Dax Raad
6465c9c44a fix openrouter caching 2025-07-19 15:11:21 -04:00
Dax Raad
4699739814 shitty hack for terrible charm bubbletea performance 2025-07-19 15:00:11 -04:00
Dax Raad
c1d87c32a2 remove log level from config 2025-07-19 13:37:02 -04:00
Aiden Cline
9c5d9be33a fix: bullet display (#1148) 2025-07-19 12:36:50 -05:00
Aiden Cline
97d9c851e6 fix: escape ansi sequences (#1139) 2025-07-19 12:02:24 -05:00
Dax Raad
76bd702992 docs: fix typo 2025-07-19 12:45:33 -04:00
Yihui Khuu
50c453e577 feat(tui): collapse session header into single line when sharing is disabled (#1145) 2025-07-19 11:43:04 -05:00
Dax Raad
86d5b25d18 pass through model.options properly without having to nest it under provider name. you may have to update your configs see https://opencode.ai/docs/models/#openrouter for an example 2025-07-19 12:41:58 -04:00
Tom
2b44dbdbf1 fix: prevent sparse spacing in hyphenated words (#1102) 2025-07-19 09:28:40 -05:00
Dax Raad
4bbbbac5f6 vercel ai gateway 2025-07-19 10:08:36 -04:00
GitHub Action
3c3a997d2a ignore: update download stats 2025-07-19 2025-07-19 12:04:11 +00:00
CodinCat
1676f8b5dd fix table heading rendering (#1138) 2025-07-18 20:17:22 -05:00
Dax Raad
c87a7469a0 ci: rollback install script 2025-07-18 18:57:58 -04:00
Michael Hanson
132e26ddbf docs: Clarify MCP config instructions (#1026) 2025-07-18 16:04:29 -04:00
Rami Chowdhury
f1da70b1de feat(provider): add Gemini tool schema sanitization (#1132) 2025-07-18 16:02:54 -04:00
Aiden Cline
5c9d1910af fix: func called before definition (#1134) 2025-07-18 15:00:32 -05:00
Timo Clasen
18abcab208 feat(config): make small model configurable (#1030) 2025-07-18 14:16:50 -04:00
opencode-agent[bot]
01e7dc2d02 Added install dir priority & user feedback (#1129)
Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com>
Co-authored-by: thdxr <thdxr@users.noreply.github.com>
2025-07-18 14:15:10 -04:00
adamdotdevin
611854e4b6 feat(tui): simpler layout, always stretched 2025-07-18 13:03:27 -05:00
67 changed files with 2997 additions and 293 deletions

View File

@@ -2,6 +2,10 @@ name: publish-github-action
on:
workflow_dispatch:
push:
tags:
- "github-v*.*.*"
- "!github-v1"
concurrency: ${{ github.workflow }}-${{ github.ref }}
@@ -18,12 +22,9 @@ jobs:
- run: git fetch --force --tags
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.17
- name: Publish
run: |
git config --global user.email "opencode@sst.dev"
git config --global user.name "opencode"
./scripts/publish-github-action.ts
./script/publish
working-directory: ./sdks/github

36
.github/workflows/publish-vscode.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: publish-vscode
on:
workflow_dispatch:
push:
tags:
- "vscode-v*.*.*"
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.17
- run: git fetch --force --tags
- run: bun install -g @vscode/vsce
- name: Publish
run: |
bun install
./script/publish
working-directory: ./sdks/vscode
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}
OPENVSX_TOKEN: ${{ secrets.OPENVSX_TOKEN }}

View File

@@ -7,6 +7,8 @@ on:
- dev
tags:
- "*"
- "!vscode-v*"
- "!github-v*"
concurrency: ${{ github.workflow }}-${{ github.ref }}

View File

@@ -33,6 +33,21 @@ paru -S opencode-bin # Arch Linux
> [!TIP]
> Remove versions older than 0.1.x before installing.
#### Installation Directory
The install script respects the following priority order for the installation path:
1. `$OPENCODE_INSTALL_DIR` - Custom installation directory
2. `$XDG_BIN_DIR` - XDG Base Directory Specification compliant path
3. `$HOME/bin` - Standard user binary directory (if exists or can be created)
4. `$HOME/.opencode/bin` - Default fallback
```bash
# Examples
OPENCODE_INSTALL_DIR=/usr/local/bin curl -fsSL https://opencode.ai/install | bash
XDG_BIN_DIR=$HOME/.local/bin curl -fsSL https://opencode.ai/install | bash
```
### Documentation
For more info on how to configure opencode [**head over to our docs**](https://opencode.ai/docs).

View File

@@ -21,3 +21,5 @@
| 2025-07-17 | 66,684 (+4,371) | 100,048 (+4,790) | 166,732 (+9,161) |
| 2025-07-18 | 70,379 (+3,695) | 102,587 (+2,539) | 172,966 (+6,234) |
| 2025-07-18 | 70,380 (+1) | 102,587 (+0) | 172,967 (+1) |
| 2025-07-19 | 73,497 (+3,117) | 105,904 (+3,317) | 179,401 (+6,434) |
| 2025-07-20 | 76,453 (+2,956) | 109,044 (+3,140) | 185,497 (+6,096) |

View File

@@ -493,7 +493,7 @@
"@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="],
"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
"@types/bun": ["@types/bun@1.2.19", "", { "dependencies": { "bun-types": "1.2.19" } }, "sha512-d9ZCmrH3CJ2uYKXQIUuZ/pUnTqIvLDS0SK7pFmbx8ma+ziH/FRMoAq5bYpRG7y+w1gl+HgyNZbtqgMq4W4e2Lg=="],
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
@@ -627,7 +627,7 @@
"buffer": ["buffer@4.9.2", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", "isarray": "^1.0.0" } }, "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg=="],
"bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
"bun-types": ["bun-types@1.2.19", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-uAOTaZSPuYsWIXRpj7o56Let0g/wjihKCkeRqUBhlLVM/Bt+Fj9xTo+LhC1OV1XDaGkz4hNC80et5xgy+9KTHQ=="],
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],

View File

@@ -186,4 +186,3 @@ if [ -n "${GITHUB_ACTIONS-}" ] && [ "${GITHUB_ACTIONS}" == "true" ]; then
echo "$INSTALL_DIR" >> $GITHUB_PATH
print_message info "Added $INSTALL_DIR to \$GITHUB_PATH"
fi

View File

@@ -1,5 +1,19 @@
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"openrouter": {
"models": {
"moonshotai/kimi-k2": {
"options": {
"provider": {
"order": ["baseten"],
"allow_fallbacks": false
}
}
}
}
}
},
"mcp": {
"weather": {
"type": "local",

View File

@@ -97,20 +97,21 @@ if (!snapshot) {
.then((res) => res.json())
.then((data) => data.commits || [])
const notes = commits
.map((commit: any) => `- ${commit.commit.message.split("\n")[0]}`)
.filter((x: string) => {
const lower = x.toLowerCase()
return (
!lower.includes("ignore:") &&
!lower.includes("chore:") &&
!lower.includes("ci:") &&
!lower.includes("wip:") &&
!lower.includes("docs:") &&
!lower.includes("doc:")
)
})
.join("\n")
const notes =
commits
.map((commit: any) => `- ${commit.commit.message.split("\n")[0]}`)
.filter((x: string) => {
const lower = x.toLowerCase()
return (
!lower.includes("ignore:") &&
!lower.includes("chore:") &&
!lower.includes("ci:") &&
!lower.includes("wip:") &&
!lower.includes("docs:") &&
!lower.includes("doc:")
)
})
.join("\n") || "No notable changes"
if (!dry) await $`gh release create v${version} --title "v${version}" --notes ${notes} ./dist/*.zip`
@@ -152,6 +153,7 @@ if (!snapshot) {
for (const pkg of ["opencode", "opencode-bin"]) {
await $`rm -rf ./dist/aur-${pkg}`
await $`git clone ssh://aur@aur.archlinux.org/${pkg}.git ./dist/aur-${pkg}`
await $`cd ./dist/aur-${pkg} && git checkout master`
await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild.replace("${pkg}", pkg))
await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`

View File

@@ -4,11 +4,12 @@ import path from "path"
export const AuthCopilot = lazy(async () => {
const file = Bun.file(path.join(Global.Path.state, "plugin", "copilot.ts"))
const exists = await file.exists()
const response = fetch("https://raw.githubusercontent.com/sst/opencode-github-copilot/refs/heads/main/auth.ts")
.then((x) => Bun.write(file, x))
.catch(() => {})
if (!file.exists()) {
if (!exists) {
const worked = await response
if (!worked) return
}

View File

@@ -78,6 +78,8 @@ export const AuthLoginCommand = cmd({
"github-copilot": 1,
openai: 2,
google: 3,
openrouter: 4,
vercel: 5,
}
let provider = await prompts.select({
message: "Select provider",
@@ -108,7 +110,7 @@ export const AuthLoginCommand = cmd({
if (provider === "other") {
provider = await prompts.text({
message: "Enter provider id",
validate: (x) => (x.match(/^[a-z-]+$/) ? undefined : "a-z and hyphens only"),
validate: (x) => (x.match(/^[0-9a-z-]+$/) ? undefined : "a-z, 0-9 and hyphens only"),
})
if (prompts.isCancel(provider)) throw new UI.CancelledError()
provider = provider.replace(/^@ai-sdk\//, "")
@@ -265,6 +267,10 @@ export const AuthLoginCommand = cmd({
return
}
if (provider === "vercel") {
prompts.log.info("You can create an api key in the dashboard")
}
const key = await prompts.password({
message: "Enter your API key",
validate: (x) => (x.length > 0 ? undefined : "Required"),

View File

@@ -84,40 +84,22 @@ export const InstallGithubCommand = cmd({
let provider = await prompts.select({
message: "Select provider",
maxItems: 8,
options: [
...pipe(
providers,
values(),
sortBy(
(x) => priority[x.id] ?? 99,
(x) => x.name ?? x.id,
),
map((x) => ({
label: x.name,
value: x.id,
hint: priority[x.id] === 0 ? "recommended" : undefined,
})),
options: pipe(
providers,
values(),
sortBy(
(x) => priority[x.id] ?? 99,
(x) => x.name ?? x.id,
),
{
value: "other",
label: "Other",
},
],
map((x) => ({
label: x.name,
value: x.id,
hint: priority[x.id] === 0 ? "recommended" : undefined,
})),
),
})
if (prompts.isCancel(provider)) throw new UI.CancelledError()
if (provider === "other") {
provider = await prompts.text({
message: "Enter provider id",
validate: (x) => (x.match(/^[a-z-]+$/) ? undefined : "a-z and hyphens only"),
})
if (prompts.isCancel(provider)) throw new UI.CancelledError()
provider = provider.replace(/^@ai-sdk\//, "")
if (prompts.isCancel(provider)) throw new UI.CancelledError()
prompts.log.warn(
`This only stores a credential for ${provider} - you will need configure it in opencode.json, check the docs for examples.`,
)
}
return provider
}
@@ -211,7 +193,11 @@ on:
jobs:
opencode:
if: startsWith(github.event.comment.body, 'hey opencode')
if: |
startsWith(github.event.comment.body, 'opencode') ||
startsWith(github.event.comment.body, 'hi opencode') ||
startsWith(github.event.comment.body, 'hey opencode') ||
contains(github.event.comment.body, '@opencode-agent')
runs-on: ubuntu-latest
permissions:
id-token: write

View File

@@ -31,9 +31,6 @@ export namespace Config {
const os = await import("os")
result.username = os.userInfo().username
}
if (!result.layout) {
result.layout = "auto"
}
log.info("loaded", result)
@@ -152,6 +149,12 @@ export namespace Config {
autoupdate: z.boolean().optional().describe("Automatically update to the latest version"),
disabled_providers: z.array(z.string()).optional().describe("Disable providers that are loaded automatically"),
model: z.string().describe("Model to use in the format of provider/model, eg anthropic/claude-2").optional(),
small_model: z
.string()
.describe(
"Small model to use for tasks like summarization and title generation in the format of provider/model",
)
.optional(),
username: z
.string()
.optional()
@@ -164,7 +167,6 @@ export namespace Config {
.catchall(Mode)
.optional()
.describe("Modes configuration, see https://opencode.ai/docs/modes"),
log_level: Log.Level.optional().describe("Minimum log level to write to log files"),
provider: z
.record(
ModelsDev.Provider.partial().extend({
@@ -176,7 +178,7 @@ export namespace Config {
.describe("Custom provider configurations and model overrides"),
mcp: z.record(z.string(), Mcp).optional().describe("MCP (Model Context Protocol) server configurations"),
instructions: z.array(z.string()).optional().describe("Additional instruction files or patterns to include"),
layout: Layout.optional().describe("Layout to use for the TUI"),
layout: Layout.optional().describe("@deprecated Always uses stretch layout."),
experimental: z
.object({
hook: z

View File

@@ -31,9 +31,13 @@ export namespace ConfigHooks {
}
})
Bus.subscribe(Session.Event.Idle, async () => {
Bus.subscribe(Session.Event.Idle, async (payload) => {
const cfg = await Config.get()
if (cfg.experimental?.hook?.session_completed) {
const session = await Session.get(payload.properties.sessionID)
// Only fire hook for top-level sessions (not subagent sessions)
if (session.parentID) return
for (const item of cfg.experimental.hook.session_completed) {
log.info("session_completed", {
command: item.command,

View File

@@ -27,7 +27,7 @@ await Promise.all([
fs.mkdir(Global.Path.state, { recursive: true }),
])
const CACHE_VERSION = "2"
const CACHE_VERSION = "3"
const version = await Bun.file(path.join(Global.Path.cache, "version"))
.text()

View File

@@ -44,25 +44,21 @@ const cli = yargs(hideBin(process.argv))
describe: "print logs to stderr",
type: "boolean",
})
.middleware(async () => {
await Log.init({ print: process.argv.includes("--print-logs"), dev: Installation.isDev() })
try {
const { Config } = await import("./config/config")
const { App } = await import("./app/app")
App.provide({ cwd: process.cwd() }, async () => {
const cfg = await Config.get()
if (cfg.log_level) {
Log.setLevel(cfg.log_level as Log.Level)
} else {
const defaultLevel = Installation.isDev() ? "DEBUG" : "INFO"
Log.setLevel(defaultLevel)
}
})
} catch (e) {
Log.Default.error("failed to load config", { error: e })
}
.option("log-level", {
describe: "log level",
type: "string",
choices: ["DEBUG", "INFO", "WARN", "ERROR"],
})
.middleware(async (opts) => {
await Log.init({
print: process.argv.includes("--print-logs"),
dev: Installation.isDev(),
level: (() => {
if (opts.logLevel) return opts.logLevel as Log.Level
if (Installation.isDev()) return "DEBUG"
return "INFO"
})(),
})
Log.Default.info("opencode", {
version: Installation.VERSION,

View File

@@ -367,7 +367,10 @@ export namespace Provider {
const pkg = provider.npm ?? provider.id
const mod = await import(await BunProc.install(pkg, "beta"))
const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
const loaded = fn(s.providers[provider.id]?.options)
const loaded = fn({
name: provider.id,
...s.providers[provider.id]?.options,
})
s.sdk.set(provider.id, loaded)
return loaded as SDK
})().catch((e) => {
@@ -416,6 +419,13 @@ export namespace Provider {
}
export async function getSmallModel(providerID: string) {
const cfg = await Config.get()
if (cfg.small_model) {
const parsed = parseModel(cfg.small_model)
return getModel(parsed.providerID, parsed.modelID)
}
const provider = await state().then((state) => state.providers[providerID])
if (!provider) return
const priority = ["3-5-haiku", "3.5-haiku", "gemini-2.5-flash"]
@@ -487,7 +497,10 @@ export namespace Provider {
...t,
parameters: optionalToNullable(t.parameters),
})),
google: TOOLS,
google: TOOLS.map((t) => ({
...t,
parameters: sanitizeGeminiParameters(t.parameters),
})),
}
export async function tools(providerID: string) {
@@ -501,6 +514,60 @@ export namespace Provider {
return TOOL_MAPPING[providerID] ?? TOOLS
}
function sanitizeGeminiParameters(schema: z.ZodTypeAny, visited = new Set()): z.ZodTypeAny {
if (!schema || visited.has(schema)) {
return schema
}
visited.add(schema)
if (schema instanceof z.ZodDefault) {
const innerSchema = schema.removeDefault()
// Handle Gemini's incompatibility with `default` on `anyOf` (unions).
if (innerSchema instanceof z.ZodUnion) {
// The schema was `z.union(...).default(...)`, which is not allowed.
// We strip the default and return the sanitized union.
return sanitizeGeminiParameters(innerSchema, visited)
}
// Otherwise, the default is on a regular type, which is allowed.
// We recurse on the inner type and then re-apply the default.
return sanitizeGeminiParameters(innerSchema, visited).default(schema._def.defaultValue())
}
if (schema instanceof z.ZodOptional) {
return z.optional(sanitizeGeminiParameters(schema.unwrap(), visited))
}
if (schema instanceof z.ZodObject) {
const newShape: Record<string, z.ZodTypeAny> = {}
for (const [key, value] of Object.entries(schema.shape)) {
newShape[key] = sanitizeGeminiParameters(value as z.ZodTypeAny, visited)
}
return z.object(newShape)
}
if (schema instanceof z.ZodArray) {
return z.array(sanitizeGeminiParameters(schema.element, visited))
}
if (schema instanceof z.ZodUnion) {
// This schema corresponds to `anyOf` in JSON Schema.
// We recursively sanitize each option in the union.
const sanitizedOptions = schema.options.map((option: z.ZodTypeAny) => sanitizeGeminiParameters(option, visited))
return z.union(sanitizedOptions as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]])
}
if (schema instanceof z.ZodString) {
const newSchema = z.string({ description: schema.description })
const safeChecks = ["min", "max", "length", "regex", "startsWith", "endsWith", "includes", "trim"]
// rome-ignore lint/suspicious/noExplicitAny: <explanation>
;(newSchema._def as any).checks = (schema._def as z.ZodStringDef).checks.filter((check) =>
safeChecks.includes(check.kind),
)
return newSchema
}
return schema
}
function optionalToNullable(schema: z.ZodTypeAny): z.ZodTypeAny {
if (schema instanceof z.ZodObject) {
const shape = schema.shape

View File

@@ -13,22 +13,15 @@ export namespace ProviderTransform {
anthropic: {
cacheControl: { type: "ephemeral" },
},
openaiCompatible: {
openrouter: {
cache_control: { type: "ephemeral" },
},
}
}
}
if (providerID === "amazon-bedrock" || modelID.includes("anthropic")) {
const system = msgs.filter((msg) => msg.role === "system").slice(0, 2)
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
for (const msg of unique([...system, ...final])) {
msg.providerOptions = {
...msg.providerOptions,
bedrock: {
cachePoint: { type: "ephemeral" },
},
openaiCompatible: {
cache_control: { type: "ephemeral" },
},
}
}
}

View File

@@ -325,6 +325,7 @@ export namespace Session {
providerID: z.string(),
modelID: z.string(),
mode: z.string().optional(),
tools: z.record(z.boolean()).optional(),
parts: z.array(
z.discriminatedUnion("type", [
MessageV2.TextPart.omit({
@@ -489,6 +490,12 @@ export namespace Session {
synthetic: true,
text: result.output,
},
{
...part,
id: part.id ?? Identifier.ascending("part"),
messageID: userMsg.id,
sessionID: input.sessionID,
},
]
}
@@ -504,13 +511,14 @@ export namespace Session {
synthetic: true,
},
{
id: Identifier.ascending("part"),
id: part.id ?? Identifier.ascending("part"),
messageID: userMsg.id,
sessionID: input.sessionID,
type: "file",
url: `data:${part.mime};base64,` + Buffer.from(await file.bytes()).toString("base64"),
mime: part.mime,
filename: part.filename!,
source: part.source,
},
]
}
@@ -539,8 +547,10 @@ export namespace Session {
if (msgs.length === 0 && !session.parentID) {
const small = (await Provider.getSmallModel(input.providerID)) ?? model
generateText({
maxOutputTokens: input.providerID === "google" ? 1024 : 20,
providerOptions: small.info.options,
maxOutputTokens: small.info.reasoning ? 1024 : 20,
providerOptions: {
[input.providerID]: small.info.options,
},
messages: [
...SystemPrompt.title(input.providerID).map(
(x): ModelMessage => ({
@@ -616,6 +626,7 @@ export namespace Session {
for (const item of await Provider.tools(input.providerID)) {
if (mode.tools[item.id] === false) continue
if (input.tools?.[item.id] === false) continue
if (session.parentID && item.id === "task") continue
tools[item.id] = tool({
id: item.id as any,
@@ -685,7 +696,9 @@ export namespace Session {
maxOutputTokens: outputLimit,
abortSignal: abort.signal,
stopWhen: stepCountIs(1000),
providerOptions: model.info.options,
providerOptions: {
[input.providerID]: model.info.options,
},
messages: [
...system.map(
(x): ModelMessage => ({

View File

@@ -4,6 +4,7 @@ import { NamedError } from "../util/error"
import { Message } from "./message"
import { convertToModelMessages, type ModelMessage, type UIMessage } from "ai"
import { Identifier } from "../id/id"
import { LSP } from "../lsp"
export namespace MessageV2 {
export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
@@ -118,11 +119,45 @@ export namespace MessageV2 {
})
export type ToolPart = z.infer<typeof ToolPart>
const FilePartSourceBase = z.object({
text: z
.object({
value: z.string(),
start: z.number().int(),
end: z.number().int(),
})
.openapi({
ref: "FilePartSourceText",
}),
})
export const FileSource = FilePartSourceBase.extend({
type: z.literal("file"),
path: z.string(),
}).openapi({
ref: "FileSource",
})
export const SymbolSource = FilePartSourceBase.extend({
type: z.literal("symbol"),
path: z.string(),
range: LSP.Range,
name: z.string(),
kind: z.number().int(),
}).openapi({
ref: "SymbolSource",
})
export const FilePartSource = z.discriminatedUnion("type", [FileSource, SymbolSource]).openapi({
ref: "FilePartSource",
})
export const FilePart = PartBase.extend({
type: z.literal("file"),
mime: z.string(),
filename: z.string().optional(),
url: z.string(),
source: FilePartSource.optional(),
}).openapi({
ref: "FilePart",
})
@@ -394,7 +429,8 @@ export namespace MessageV2 {
text: part.text,
},
]
if (part.type === "file")
// text/plain files are converted into text parts, ignore them
if (part.type === "file" && part.mime !== "text/plain")
return [
{
type: "file",

View File

@@ -41,6 +41,10 @@ export const TaskTool = Tool.define({
sessionID: session.id,
modelID: msg.modelID,
providerID: msg.providerID,
tools: {
todoread: false,
todowrite: false,
},
parts: [
{
id: Identifier.ascending("part"),

View File

@@ -14,18 +14,10 @@ export namespace Log {
ERROR: 3,
}
let currentLevel: Level = "INFO"
let level: Level = "INFO"
export function setLevel(level: Level) {
currentLevel = level
}
export function getLevel(): Level {
return currentLevel
}
function shouldLog(level: Level): boolean {
return levelPriority[level] >= levelPriority[currentLevel]
function shouldLog(input: Level): boolean {
return levelPriority[input] >= levelPriority[level]
}
export type Logger = {
@@ -60,6 +52,7 @@ export namespace Log {
}
export async function init(options: Options) {
if (options.level) level = options.level
const dir = path.join(Global.Path.data, "log")
await fs.mkdir(dir, { recursive: true })
cleanup(dir)

View File

@@ -1,2 +1,4 @@
opencode-test
cmd/opencode/opencode
opencode

View File

@@ -54,7 +54,9 @@ func main() {
option.WithBaseURL(url),
)
apiHandler := util.NewAPILogHandler(httpClient, "tui", slog.LevelDebug)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
apiHandler := util.NewAPILogHandler(ctx, httpClient, "tui", slog.LevelDebug)
logger := slog.New(apiHandler)
slog.SetDefault(logger)
@@ -68,8 +70,6 @@ func main() {
}()
// Create main context for the application
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
app_, err := app.New(ctx, version, appInfo, modes, httpClient, model, prompt, mode)
if err != nil {
@@ -91,6 +91,9 @@ func main() {
stream := httpClient.Event.ListStreaming(ctx)
for stream.Next() {
evt := stream.Current().AsUnion()
if _, ok := evt.(opencode.EventListResponseEventStorageWrite); ok {
continue
}
program.Send(evt)
}
if err := stream.Err(); err != nil {

View File

@@ -5,12 +5,12 @@ go 1.24.0
require (
github.com/BurntSushi/toml v1.5.0
github.com/alecthomas/chroma/v2 v2.18.0
github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4
github.com/charmbracelet/glamour v0.10.0
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3
github.com/charmbracelet/x/ansi v0.9.3
github.com/charmbracelet/x/input v0.3.7
github.com/google/uuid v1.6.0
github.com/lithammer/fuzzysearch v1.1.8
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6
@@ -34,6 +34,7 @@ require (
github.com/atombender/go-jsonschema v0.20.0 // indirect
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
github.com/charmbracelet/x/input v0.3.7 // indirect
github.com/charmbracelet/x/windows v0.2.1 // indirect
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect

View File

@@ -20,6 +20,8 @@ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWp
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1 h1:swACzss0FjnyPz1enfX56GKkLiuKg5FlyVmOLIlU2kE=
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.4 h1:UgUuKKvBwgqm2ZEL+sKv/OLeavrUb4gfHgdxe6oIOno=

View File

@@ -463,28 +463,41 @@ func (p *Parser) parseWin32InputKeyEvent(state *win32InputState, vkc uint16, _ u
baseCode = KeyMediaStop
case vkc == xwindows.VK_MEDIA_PLAY_PAUSE:
baseCode = KeyMediaPlayPause
case vkc == xwindows.VK_OEM_1:
baseCode = ';'
case vkc == xwindows.VK_OEM_PLUS:
baseCode = '+'
case vkc == xwindows.VK_OEM_COMMA:
baseCode = ','
case vkc == xwindows.VK_OEM_MINUS:
baseCode = '-'
case vkc == xwindows.VK_OEM_PERIOD:
baseCode = '.'
case vkc == xwindows.VK_OEM_2:
baseCode = '/'
case vkc == xwindows.VK_OEM_3:
baseCode = '`'
case vkc == xwindows.VK_OEM_4:
baseCode = '['
case vkc == xwindows.VK_OEM_5:
baseCode = '\\'
case vkc == xwindows.VK_OEM_6:
baseCode = ']'
case vkc == xwindows.VK_OEM_7:
baseCode = '\''
case vkc == xwindows.VK_OEM_1, vkc == xwindows.VK_OEM_PLUS, vkc == xwindows.VK_OEM_COMMA,
vkc == xwindows.VK_OEM_MINUS, vkc == xwindows.VK_OEM_PERIOD, vkc == xwindows.VK_OEM_2,
vkc == xwindows.VK_OEM_3, vkc == xwindows.VK_OEM_4, vkc == xwindows.VK_OEM_5,
vkc == xwindows.VK_OEM_6, vkc == xwindows.VK_OEM_7:
// Use the actual character provided by Windows for current keyboard layout
// instead of hardcoded US layout mappings
if !unicode.IsControl(r) && unicode.IsPrint(r) {
baseCode = r
} else {
// Fallback to original hardcoded mappings for non-printable cases
switch vkc {
case xwindows.VK_OEM_1:
baseCode = ';'
case xwindows.VK_OEM_PLUS:
baseCode = '+'
case xwindows.VK_OEM_COMMA:
baseCode = ','
case xwindows.VK_OEM_MINUS:
baseCode = '-'
case xwindows.VK_OEM_PERIOD:
baseCode = '.'
case xwindows.VK_OEM_2:
baseCode = '/'
case xwindows.VK_OEM_3:
baseCode = '`'
case xwindows.VK_OEM_4:
baseCode = '['
case xwindows.VK_OEM_5:
baseCode = '\\'
case xwindows.VK_OEM_6:
baseCode = ']'
case xwindows.VK_OEM_7:
baseCode = '\''
}
}
}
if utf16.IsSurrogate(r) {
@@ -500,20 +513,29 @@ func (p *Parser) parseWin32InputKeyEvent(state *win32InputState, vkc uint16, _ u
// XXX: Should this be a KeyMod?
altGr := cks&(xwindows.LEFT_CTRL_PRESSED|xwindows.RIGHT_ALT_PRESSED) == xwindows.LEFT_CTRL_PRESSED|xwindows.RIGHT_ALT_PRESSED
// FIXED: Remove numlock and scroll lock states when checking for printable text
// These lock states shouldn't affect normal typing
cksForTextCheck := cks &^ (xwindows.NUMLOCK_ON | xwindows.SCROLLLOCK_ON)
var text string
keyCode := baseCode
if !unicode.IsControl(r) {
rw := utf8.EncodeRune(utf8Buf[:], r)
keyCode, _ = utf8.DecodeRune(utf8Buf[:rw])
if unicode.IsPrint(keyCode) && (cks == 0 ||
cks == xwindows.SHIFT_PRESSED ||
cks == xwindows.CAPSLOCK_ON ||
if unicode.IsPrint(keyCode) && (cksForTextCheck == 0 ||
cksForTextCheck == xwindows.SHIFT_PRESSED ||
cksForTextCheck == xwindows.CAPSLOCK_ON ||
altGr) {
// If the control key state is 0, shift is pressed, or caps lock
// then the key event is a printable event i.e. [text] is not empty.
text = string(keyCode)
}
}
// Special case: numeric keypad divide should produce "/" text on all layouts (fix french keyboard layout)
if baseCode == KeyKpDivide {
text = "/"
}
key.Code = keyCode
key.Text = text

View File

@@ -1,4 +0,0 @@
package app
const MAX_CONTAINER_WIDTH = 86
const EDIT_DIFF_MAX_WIDTH = 180

View File

@@ -64,10 +64,7 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = min(msg.Width-4, app.MAX_CONTAINER_WIDTH)
if m.app.Config.Layout == opencode.LayoutConfigStretch {
m.width = msg.Width - 4
}
m.width = msg.Width - 4
return m, nil
case spinner.TickMsg:
m.spinner, cmd = m.spinner.Update(msg)
@@ -180,6 +177,11 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
func (m *editorComponent) Content() string {
width := m.width
if m.app.Session.ID == "" {
width = min(width, 80)
}
t := theme.CurrentTheme()
base := styles.NewStyle().Foreground(t.Text()).Background(t.Background()).Render
muted := styles.NewStyle().Foreground(t.TextMuted()).Background(t.Background()).Render
@@ -188,7 +190,7 @@ func (m *editorComponent) Content() string {
Bold(true)
prompt := promptStyle.Render(">")
m.textarea.SetWidth(m.width - 6)
m.textarea.SetWidth(width - 6)
textarea := lipgloss.JoinHorizontal(
lipgloss.Top,
prompt,
@@ -200,7 +202,7 @@ func (m *editorComponent) Content() string {
}
textarea = styles.NewStyle().
Background(t.BackgroundElement()).
Width(m.width).
Width(width).
PaddingTop(1).
PaddingBottom(1).
BorderStyle(lipgloss.ThickBorder()).
@@ -236,7 +238,7 @@ func (m *editorComponent) Content() string {
model = muted(m.app.Provider.Name) + base(" "+m.app.Model.Name)
}
space := m.width - 2 - lipgloss.Width(model) - lipgloss.Width(hint)
space := width - 2 - lipgloss.Width(model) - lipgloss.Width(hint)
spacer := styles.NewStyle().Background(t.Background()).Width(space).Render("")
info := hint + spacer + model
@@ -247,9 +249,14 @@ func (m *editorComponent) Content() string {
}
func (m *editorComponent) View() string {
width := m.width
if m.app.Session.ID == "" {
width = min(width, 80)
}
if m.Lines() > 1 {
return lipgloss.Place(
m.width,
width,
5,
lipgloss.Center,
lipgloss.Center,

View File

@@ -264,6 +264,8 @@ func renderToolDetails(
toolCall opencode.ToolPart,
width int,
) string {
measure := util.Measure("chat.renderToolDetails")
defer measure("tool", toolCall.Tool)
ignoredTools := []string{"todoread"}
if slices.Contains(ignoredTools, toolCall.Tool) {
return ""
@@ -368,12 +370,14 @@ func renderToolDetails(
}
}
case "bash":
command := toolInputMap["command"].(string)
body = fmt.Sprintf("```console\n$ %s\n", command)
stdout := metadata["stdout"]
if stdout != nil {
command := toolInputMap["command"].(string)
body = fmt.Sprintf("```console\n> %s\n%s```", command, stdout)
body = util.ToMarkdown(body, width, backgroundColor)
body += ansi.Strip(fmt.Sprintf("%s", stdout))
}
body += "```"
body = util.ToMarkdown(body, width, backgroundColor)
case "webfetch":
if format, ok := toolInputMap["format"].(string); ok && result != nil {
body = *result

View File

@@ -2,9 +2,9 @@ package chat
import (
"fmt"
"log/slog"
"strings"
"github.com/charmbracelet/bubbles/v2/viewport"
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/lipgloss/v2"
"github.com/sst/opencode-sdk-go"
@@ -15,6 +15,7 @@ import (
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
"github.com/sst/opencode/internal/util"
"github.com/sst/opencode/internal/viewport"
)
type MessagesComponent interface {
@@ -52,6 +53,8 @@ func (m *messagesComponent) Init() tea.Cmd {
}
func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
measure := util.Measure("messages.Update")
defer measure("from", fmt.Sprintf("%T", msg))
var cmds []tea.Cmd
switch msg := msg.(type) {
case tea.WindowSizeMsg:
@@ -64,7 +67,7 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.height = msg.Height - 7
m.viewport.SetWidth(m.width)
m.loading = true
return m, m.Reload()
return m, m.renderView()
case app.SendMsg:
m.viewport.GotoBottom()
m.tail = true
@@ -72,15 +75,15 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case dialog.ThemeSelectedMsg:
m.cache.Clear()
m.loading = true
return m, m.Reload()
return m, m.renderView()
case ToggleToolDetailsMsg:
m.showToolDetails = !m.showToolDetails
return m, m.Reload()
return m, m.renderView()
case app.SessionLoadedMsg, app.SessionClearedMsg:
m.cache.Clear()
m.tail = true
m.loading = true
return m, m.Reload()
return m, m.renderView()
case opencode.EventListResponseEventSessionUpdated:
if msg.Properties.Info.ID == m.app.Session.ID {
@@ -99,34 +102,33 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.lineCount = msg.lineCount
m.rendering = false
m.loading = false
m.viewport.SetHeight(m.height - lipgloss.Height(m.header))
m.viewport.SetContent(msg.content)
if m.tail {
m.viewport.GotoBottom()
}
m.tail = m.viewport.AtBottom()
m.viewport = msg.viewport
m.header = msg.header
if m.dirty {
cmds = append(cmds, m.renderView())
}
}
m.tail = m.viewport.AtBottom()
viewport, cmd := m.viewport.Update(msg)
m.viewport = viewport
m.tail = m.viewport.AtBottom()
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
type renderCompleteMsg struct {
content string
viewport viewport.Model
header string
partCount int
lineCount int
}
func (m *messagesComponent) renderView() tea.Cmd {
m.header = m.renderHeader()
if m.rendering {
slog.Debug("pending render, skipping")
m.dirty = true
return func() tea.Msg {
return nil
@@ -135,7 +137,11 @@ func (m *messagesComponent) renderView() tea.Cmd {
m.dirty = false
m.rendering = true
viewport := m.viewport
tail := m.tail
return func() tea.Msg {
header := m.renderHeader()
measure := util.Measure("messages.renderView")
defer measure()
@@ -146,10 +152,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
orphanedToolCalls := make([]opencode.ToolPart, 0)
width := min(m.width, app.MAX_CONTAINER_WIDTH)
if m.app.Config.Layout == opencode.LayoutConfigStretch {
width = m.width
}
width := m.width // always use full width
for _, message := range m.app.Messages {
var content string
@@ -231,6 +234,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
}
case opencode.AssistantMessage:
messageMeasure := util.Measure("messages.Render")
hasTextPart := false
for partIndex, p := range message.Parts {
switch part := p.(type) {
@@ -320,13 +324,6 @@ func (m *messagesComponent) renderView() tea.Cmd {
continue
}
width := width
if m.app.Config.Layout == opencode.LayoutConfigAuto &&
part.Tool == "edit" &&
part.State.Error == "" {
width = min(m.width, app.EDIT_DIFF_MAX_WIDTH)
}
if part.State.Status == opencode.ToolPartStateStatusCompleted || part.State.Status == opencode.ToolPartStateStatusError {
key := m.cache.GenerateKey(casted.ID,
part.ID,
@@ -369,6 +366,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
}
}
}
messageMeasure()
}
error := ""
@@ -406,8 +404,15 @@ func (m *messagesComponent) renderView() tea.Cmd {
}
content := "\n" + strings.Join(blocks, "\n\n")
viewport.SetHeight(m.height - lipgloss.Height(header))
viewport.SetContent(content)
if tail {
viewport.GotoBottom()
}
return renderCompleteMsg{
content: content,
header: header,
viewport: viewport,
partCount: partCount,
lineCount: lineCount,
}
@@ -419,26 +424,11 @@ func (m *messagesComponent) renderHeader() string {
return ""
}
headerWidth := min(m.width, app.MAX_CONTAINER_WIDTH)
if m.app.Config.Layout == opencode.LayoutConfigStretch {
headerWidth = m.width
}
headerWidth := m.width
t := theme.CurrentTheme()
base := styles.NewStyle().Foreground(t.Text()).Background(t.Background()).Render
muted := styles.NewStyle().Foreground(t.TextMuted()).Background(t.Background()).Render
headerLines := []string{}
headerLines = append(
headerLines,
util.ToMarkdown("# "+m.app.Session.Title, headerWidth-6, t.Background()),
)
share := ""
if m.app.Session.Share.URL != "" {
share = muted(m.app.Session.Share.URL + " /unshare")
} else {
share = base("/share") + muted(" to create a shareable link")
}
sessionInfo := ""
tokens := float64(0)
@@ -472,30 +462,38 @@ func (m *messagesComponent) renderHeader() string {
Background(t.Background()).
Render(formatTokensAndCost(tokens, contextWindow, cost, isSubscriptionModel))
background := t.Background()
shareEnabled := m.app.Config.Share != opencode.ConfigShareDisabled
headerText := util.ToMarkdown("# "+m.app.Session.Title, headerWidth-len(sessionInfo), t.Background())
var items []layout.FlexItem
justify := layout.JustifyEnd
if m.app.Config.Share != opencode.ConfigShareDisabled {
items = append(items, layout.FlexItem{View: share})
justify = layout.JustifySpaceBetween
if shareEnabled {
share := base("/share") + muted(" to create a shareable link")
if m.app.Session.Share.URL != "" {
share = muted(m.app.Session.Share.URL + " /unshare")
}
items = []layout.FlexItem{{View: share}, {View: sessionInfo}}
} else {
items = []layout.FlexItem{{View: headerText}, {View: sessionInfo}}
}
items = append(items, layout.FlexItem{View: sessionInfo})
background := t.Background()
headerRow := layout.Render(
layout.FlexOptions{
Background: &background,
Direction: layout.Row,
Justify: justify,
Justify: layout.JustifySpaceBetween,
Align: layout.AlignStretch,
Width: headerWidth - 6,
},
items...,
)
headerLines = append(headerLines, headerRow)
var headerLines []string
if shareEnabled {
headerLines = []string{headerText, headerRow}
} else {
headerLines = []string{headerRow}
}
header := strings.Join(headerLines, "\n")
header = styles.NewStyle().
@@ -579,13 +577,12 @@ func (m *messagesComponent) View() string {
)
}
measure := util.Measure("messages.View")
viewport := m.viewport.View()
measure()
return styles.NewStyle().
Background(t.Background()).
Render(m.header + "\n" + m.viewport.View())
}
func (m *messagesComponent) Reload() tea.Cmd {
return m.renderView()
Render(m.header + "\n" + viewport)
}
func (m *messagesComponent) PageUp() (tea.Model, tea.Cmd) {

View File

@@ -1,13 +1,13 @@
package dialog
import (
"github.com/charmbracelet/bubbles/v2/viewport"
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/sst/opencode/internal/app"
commandsComponent "github.com/sst/opencode/internal/components/commands"
"github.com/sst/opencode/internal/components/modal"
"github.com/sst/opencode/internal/layout"
"github.com/sst/opencode/internal/theme"
"github.com/sst/opencode/internal/viewport"
)
type helpDialog struct {

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"strings"
"github.com/charmbracelet/bubbles/v2/viewport"
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/sst/opencode/internal/app"
@@ -15,6 +14,7 @@ import (
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
"github.com/sst/opencode/internal/util"
"github.com/sst/opencode/internal/viewport"
)
type DiffStyle int

View File

@@ -284,7 +284,6 @@ func generateMarkdownStyleConfig(backgroundColor compat.AdaptiveColor) ansi.Styl
Table: ansi.StyleTable{
StyleBlock: ansi.StyleBlock{
StylePrimitive: ansi.StylePrimitive{
BlockPrefix: "\n",
BlockSuffix: "\n",
},
},

View File

@@ -103,6 +103,9 @@ func (a appModel) Init() tea.Cmd {
}
func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
measure := util.Measure("app.Update")
defer measure("from", fmt.Sprintf("%T", msg))
var cmd tea.Cmd
var cmds []tea.Cmd
@@ -439,7 +442,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.WindowSizeMsg:
msg.Height -= 2 // Make space for the status bar
a.width, a.height = msg.Width, msg.Height
container := min(a.width, app.MAX_CONTAINER_WIDTH)
container := min(a.width, 86)
layout.Current = &layout.LayoutInfo{
Viewport: layout.Dimensions{
Width: a.width,
@@ -525,6 +528,8 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
func (a appModel) View() string {
measure := util.Measure("app.View")
defer measure()
t := theme.CurrentTheme()
var mainLayout string
@@ -580,6 +585,8 @@ func (a appModel) openFile(filepath string) (tea.Model, tea.Cmd) {
}
func (a appModel) home() string {
measure := util.Measure("home.View")
defer measure()
t := theme.CurrentTheme()
effectiveWidth := a.width - 4
baseStyle := styles.NewStyle().Background(t.Background())
@@ -691,6 +698,8 @@ func (a appModel) home() string {
}
func (a appModel) chat() string {
measure := util.Measure("chat.View")
defer measure()
effectiveWidth := a.width - 4
t := theme.CurrentTheme()
editorView := a.editor.View()
@@ -775,7 +784,8 @@ func (a appModel) executeCommand(command commands.Command) (tea.Model, tea.Cmd)
return a, toast.NewErrorToast("Something went wrong, couldn't open editor")
}
tmpfile.Close()
c := exec.Command(editor, tmpfile.Name()) //nolint:gosec
parts := strings.Fields(editor)
c := exec.Command(parts[0], append(parts[1:], tmpfile.Name())...) //nolint:gosec
c.Stdin = os.Stdin
c.Stdout = os.Stdout
c.Stderr = os.Stderr

View File

@@ -15,16 +15,32 @@ type APILogHandler struct {
attrs []slog.Attr
groups []string
mu sync.Mutex
queue chan opencode.AppLogParams
}
func NewAPILogHandler(client *opencode.Client, service string, level slog.Level) *APILogHandler {
return &APILogHandler{
func NewAPILogHandler(ctx context.Context, client *opencode.Client, service string, level slog.Level) *APILogHandler {
result := &APILogHandler{
client: client,
service: service,
level: level,
attrs: make([]slog.Attr, 0),
groups: make([]string, 0),
queue: make(chan opencode.AppLogParams, 100_000),
}
go func() {
for {
select {
case <-ctx.Done():
return
case params := <-result.queue:
_, err := client.App.Log(context.Background(), params)
if err != nil {
slog.Error("Failed to log to API", "error", err)
}
}
}
}()
return result
}
func (h *APILogHandler) Enabled(_ context.Context, level slog.Level) bool {
@@ -69,13 +85,7 @@ func (h *APILogHandler) Handle(ctx context.Context, r slog.Record) error {
params.Extra = opencode.F(extra)
}
go func() {
_, err := h.client.App.Log(context.Background(), params)
if err != nil {
// Fallback: we can't log the error using slog as it would create a loop
// TODO: fallback file?
}
}()
h.queue <- params
return nil
}

View File

@@ -0,0 +1,52 @@
package util
import (
"regexp"
"strings"
"github.com/charmbracelet/lipgloss/v2"
)
// PreventHyphenBreaks replaces regular hyphens with non-breaking hyphens to prevent
// sparse word breaks in hyphenated terms like "claude-code-action".
// This improves readability by keeping hyphenated words together.
// Only preserves hyphens within words, not markdown syntax like bullet points.
func PreventHyphenBreaks(text string) string {
// Use regex to match hyphens that are between word characters
// This preserves hyphens in words like "claude-code-action" but not in "- [ ]"
re := regexp.MustCompile(`(\w)-(\w)`)
return re.ReplaceAllString(text, "$1\u2011$2")
}
// RestoreHyphens converts non-breaking hyphens back to regular hyphens.
// This should be called after text processing (like word wrapping) is complete.
func RestoreHyphens(text string) string {
return strings.ReplaceAll(text, "\u2011", "-")
}
// ProcessTextWithHyphens applies hyphen preservation to text during processing.
// It wraps the provided processFunc with hyphen handling.
func ProcessTextWithHyphens(text string, processFunc func(string) string) string {
preserved := PreventHyphenBreaks(text)
processed := processFunc(preserved)
return RestoreHyphens(processed)
}
// GetMessageContainerFrame calculates the actual horizontal frame size
// (padding + borders) for message containers based on current theme.
func GetMessageContainerFrame() int {
style := lipgloss.NewStyle().
BorderStyle(lipgloss.ThickBorder()).
BorderLeft(true).
BorderRight(true).
PaddingLeft(2).
PaddingRight(2)
return style.GetHorizontalFrameSize()
}
// GetMarkdownContainerFrame calculates the actual horizontal frame size
// for markdown containers based on current theme.
func GetMarkdownContainerFrame() int {
// Markdown containers use the same styling as message containers
return GetMessageContainerFrame()
}

View File

@@ -40,8 +40,8 @@ func IsWsl() bool {
func Measure(tag string) func(...any) {
startTime := time.Now()
return func(tags ...any) {
args := append([]any{"timeTakenMs", time.Since(startTime).Milliseconds()}, tags...)
return func(args ...any) {
args = append(args, []any{"timeTakenMs", time.Since(startTime).Milliseconds()}...)
slog.Debug(tag, args...)
}
}

View File

@@ -0,0 +1,141 @@
package viewport
import (
"github.com/charmbracelet/lipgloss/v2"
"github.com/charmbracelet/x/ansi"
"github.com/rivo/uniseg"
)
// parseMatches converts the given matches into highlight ranges.
//
// Assumptions:
// - matches are measured in bytes, e.g. what [regex.FindAllStringIndex] would return
// - matches were made against the given content
// - matches are in order
// - matches do not overlap
// - content is line terminated with \n only
//
// We'll then convert the ranges into [highlightInfo]s, which hold the starting
// line and the grapheme positions.
func parseMatches(
content string,
matches [][]int,
) []highlightInfo {
if len(matches) == 0 {
return nil
}
line := 0
graphemePos := 0
previousLinesOffset := 0
bytePos := 0
highlights := make([]highlightInfo, 0, len(matches))
gr := uniseg.NewGraphemes(ansi.Strip(content))
for _, match := range matches {
byteStart, byteEnd := match[0], match[1]
// hilight for this match:
hi := highlightInfo{
lines: map[int][2]int{},
}
// find the beginning of this byte range, setup current line and
// grapheme position.
for byteStart > bytePos {
if !gr.Next() {
break
}
if content[bytePos] == '\n' {
previousLinesOffset = graphemePos + 1
line++
}
graphemePos += max(1, gr.Width())
bytePos += len(gr.Str())
}
hi.lineStart = line
hi.lineEnd = line
graphemeStart := graphemePos
// loop until we find the end
for byteEnd > bytePos {
if !gr.Next() {
break
}
// if it ends with a new line, add the range, increase line, and continue
if content[bytePos] == '\n' {
colstart := max(0, graphemeStart-previousLinesOffset)
colend := max(graphemePos-previousLinesOffset+1, colstart) // +1 its \n itself
if colend > colstart {
hi.lines[line] = [2]int{colstart, colend}
hi.lineEnd = line
}
previousLinesOffset = graphemePos + 1
line++
}
graphemePos += max(1, gr.Width())
bytePos += len(gr.Str())
}
// we found it!, add highlight and continue
if bytePos == byteEnd {
colstart := max(0, graphemeStart-previousLinesOffset)
colend := max(graphemePos-previousLinesOffset, colstart)
if colend > colstart {
hi.lines[line] = [2]int{colstart, colend}
hi.lineEnd = line
}
}
highlights = append(highlights, hi)
}
return highlights
}
type highlightInfo struct {
// in which line this highlight starts and ends
lineStart, lineEnd int
// the grapheme highlight ranges for each of these lines
lines map[int][2]int
}
// coords returns the line x column of this highlight.
func (hi highlightInfo) coords() (int, int, int) {
for i := hi.lineStart; i <= hi.lineEnd; i++ {
hl, ok := hi.lines[i]
if !ok {
continue
}
return i, hl[0], hl[1]
}
return hi.lineStart, 0, 0
}
func makeHighlightRanges(
highlights []highlightInfo,
line int,
style lipgloss.Style,
) []lipgloss.Range {
result := []lipgloss.Range{}
for _, hi := range highlights {
lihi, ok := hi.lines[line]
if !ok {
continue
}
if lihi == [2]int{} {
continue
}
result = append(result, lipgloss.NewRange(lihi[0], lihi[1], style))
}
return result
}

View File

@@ -0,0 +1,56 @@
package viewport
import "github.com/charmbracelet/bubbles/v2/key"
// KeyMap defines the keybindings for the viewport. Note that you don't
// necessary need to use keybindings at all; the viewport can be controlled
// programmatically with methods like Model.LineDown(1). See the GoDocs for
// details.
type KeyMap struct {
PageDown key.Binding
PageUp key.Binding
HalfPageUp key.Binding
HalfPageDown key.Binding
Down key.Binding
Up key.Binding
Left key.Binding
Right key.Binding
}
// DefaultKeyMap returns a set of pager-like default keybindings.
func DefaultKeyMap() KeyMap {
return KeyMap{
PageDown: key.NewBinding(
key.WithKeys("pgdown", "space", "f"),
key.WithHelp("f/pgdn", "page down"),
),
PageUp: key.NewBinding(
key.WithKeys("pgup", "b"),
key.WithHelp("b/pgup", "page up"),
),
HalfPageUp: key.NewBinding(
key.WithKeys("u", "ctrl+u"),
key.WithHelp("u", "½ page up"),
),
HalfPageDown: key.NewBinding(
key.WithKeys("d", "ctrl+d"),
key.WithHelp("d", "½ page down"),
),
Up: key.NewBinding(
key.WithKeys("up", "k"),
key.WithHelp("↑/k", "up"),
),
Down: key.NewBinding(
key.WithKeys("down", "j"),
key.WithHelp("↓/j", "down"),
),
Left: key.NewBinding(
key.WithKeys("left", "h"),
key.WithHelp("←/h", "move left"),
),
Right: key.NewBinding(
key.WithKeys("right", "l"),
key.WithHelp("→/l", "move right"),
),
}
}

View File

@@ -0,0 +1,803 @@
package viewport
import (
"math"
"strings"
"github.com/charmbracelet/bubbles/v2/key"
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/lipgloss/v2"
"github.com/charmbracelet/x/ansi"
)
const (
defaultHorizontalStep = 6
)
// Option is a configuration option that works in conjunction with [New]. For
// example:
//
// timer := New(WithWidth(10, WithHeight(5)))
type Option func(*Model)
// WithWidth is an initialization option that sets the width of the
// viewport. Pass as an argument to [New].
func WithWidth(w int) Option {
return func(m *Model) {
m.width = w
}
}
// WithHeight is an initialization option that sets the height of the
// viewport. Pass as an argument to [New].
func WithHeight(h int) Option {
return func(m *Model) {
m.height = h
}
}
// New returns a new model with the given width and height as well as default
// key mappings.
func New(opts ...Option) (m Model) {
for _, opt := range opts {
opt(&m)
}
m.setInitialValues()
m.memo = &Memo{}
return m
}
type Memo struct {
dirty bool
cache string
}
func (m *Memo) View(render func() string) string {
if m.dirty {
// slog.Debug("memo dirty")
m.cache = render()
m.dirty = false
return m.cache
}
// slog.Debug("memo cache")
return m.cache
}
func (m *Memo) Invalidate() {
m.dirty = true
}
// Model is the Bubble Tea model for this viewport element.
type Model struct {
memo *Memo
width int
height int
KeyMap KeyMap
// Whether or not to wrap text. If false, it'll allow horizontal scrolling
// instead.
SoftWrap bool
// Whether or not to fill to the height of the viewport with empty lines.
FillHeight bool
// Whether or not to respond to the mouse. The mouse must be enabled in
// Bubble Tea for this to work. For details, see the Bubble Tea docs.
MouseWheelEnabled bool
// The number of lines the mouse wheel will scroll. By default, this is 3.
MouseWheelDelta int
// YOffset is the vertical scroll position.
YOffset int
// xOffset is the horizontal scroll position.
xOffset int
// horizontalStep is the number of columns we move left or right during a
// default horizontal scroll.
horizontalStep int
// YPosition is the position of the viewport in relation to the terminal
// window. It's used in high performance rendering only.
YPosition int
// Style applies a lipgloss style to the viewport. Realistically, it's most
// useful for setting borders, margins and padding.
Style lipgloss.Style
// LeftGutterFunc allows to define a [GutterFunc] that adds a column into
// the left of the viewport, which is kept when horizontal scrolling.
// This can be used for things like line numbers, selection indicators,
// show statuses, etc.
LeftGutterFunc GutterFunc
initialized bool
lines []string
longestLineWidth int
// HighlightStyle highlights the ranges set with [SetHighligths].
HighlightStyle lipgloss.Style
// SelectedHighlightStyle highlights the highlight range focused during
// navigation.
// Use [SetHighligths] to set the highlight ranges, and [HightlightNext]
// and [HihglightPrevious] to navigate.
SelectedHighlightStyle lipgloss.Style
// StyleLineFunc allows to return a [lipgloss.Style] for each line.
// The argument is the line index.
StyleLineFunc func(int) lipgloss.Style
highlights []highlightInfo
hiIdx int
}
// GutterFunc can be implemented and set into [Model.LeftGutterFunc].
//
// Example implementation showing line numbers:
//
// func(info GutterContext) string {
// if info.Soft {
// return " │ "
// }
// if info.Index >= info.TotalLines {
// return " ~ │ "
// }
// return fmt.Sprintf("%4d │ ", info.Index+1)
// }
type GutterFunc func(GutterContext) string
// NoGutter is the default gutter used.
var NoGutter = func(GutterContext) string { return "" }
// GutterContext provides context to a [GutterFunc].
type GutterContext struct {
Index int
TotalLines int
Soft bool
}
func (m *Model) setInitialValues() {
m.KeyMap = DefaultKeyMap()
m.MouseWheelEnabled = true
m.MouseWheelDelta = 3
m.initialized = true
m.horizontalStep = defaultHorizontalStep
m.LeftGutterFunc = NoGutter
}
// Init exists to satisfy the tea.Model interface for composability purposes.
func (m Model) Init() tea.Cmd {
return nil
}
// Height returns the height of the viewport.
func (m Model) Height() int {
return m.height
}
// SetHeight sets the height of the viewport.
func (m *Model) SetHeight(h int) {
m.height = h
m.memo.Invalidate()
}
// Width returns the width of the viewport.
func (m Model) Width() int {
return m.width
}
// SetWidth sets the width of the viewport.
func (m *Model) SetWidth(w int) {
m.width = w
m.memo.Invalidate()
}
// AtTop returns whether or not the viewport is at the very top position.
func (m Model) AtTop() bool {
return m.YOffset <= 0
}
// AtBottom returns whether or not the viewport is at or past the very bottom
// position.
func (m Model) AtBottom() bool {
return m.YOffset >= m.maxYOffset()
}
// PastBottom returns whether or not the viewport is scrolled beyond the last
// line. This can happen when adjusting the viewport height.
func (m Model) PastBottom() bool {
return m.YOffset > m.maxYOffset()
}
// ScrollPercent returns the amount scrolled as a float between 0 and 1.
func (m Model) ScrollPercent() float64 {
count := m.lineCount()
if m.Height() >= count {
return 1.0
}
y := float64(m.YOffset)
h := float64(m.Height())
t := float64(count)
v := y / (t - h)
return math.Max(0.0, math.Min(1.0, v))
}
// HorizontalScrollPercent returns the amount horizontally scrolled as a float
// between 0 and 1.
func (m Model) HorizontalScrollPercent() float64 {
if m.xOffset >= m.longestLineWidth-m.Width() {
return 1.0
}
y := float64(m.xOffset)
h := float64(m.Width())
t := float64(m.longestLineWidth)
v := y / (t - h)
return math.Max(0.0, math.Min(1.0, v))
}
// SetContent set the pager's text content.
// Line endings will be normalized to '\n'.
func (m *Model) SetContent(s string) {
s = strings.ReplaceAll(s, "\r\n", "\n") // normalize line endings
m.SetContentLines(strings.Split(s, "\n"))
m.memo.Invalidate()
}
// SetContentLines allows to set the lines to be shown instead of the content.
// If a given line has a \n in it, it'll be considered a [Model.SoftWrap].
// See also [Model.SetContent].
func (m *Model) SetContentLines(lines []string) {
// if there's no content, set content to actual nil instead of one empty
// line.
m.lines = lines
if len(m.lines) == 1 && ansi.StringWidth(m.lines[0]) == 0 {
m.lines = nil
}
m.longestLineWidth = maxLineWidth(m.lines)
m.ClearHighlights()
if m.YOffset > m.maxYOffset() {
m.GotoBottom()
}
m.memo.Invalidate()
}
// GetContent returns the entire content as a single string.
// Line endings are normalized to '\n'.
func (m Model) GetContent() string {
return strings.Join(m.lines, "\n")
}
// calculateLine taking soft wraping into account, returns the total viewable
// lines and the real-line index for the given yoffset.
func (m Model) calculateLine(yoffset int) (total, idx int) {
if !m.SoftWrap {
for i, line := range m.lines {
adjust := max(1, lipgloss.Height(line))
if yoffset >= total && yoffset < total+adjust {
idx = i
}
total += adjust
}
if yoffset >= total {
idx = len(m.lines)
}
return total, idx
}
maxWidth := m.maxWidth()
var gutterSize int
if m.LeftGutterFunc != nil {
gutterSize = lipgloss.Width(m.LeftGutterFunc(GutterContext{}))
}
for i, line := range m.lines {
adjust := max(1, lipgloss.Width(line)/(maxWidth-gutterSize))
if yoffset >= total && yoffset < total+adjust {
idx = i
}
total += adjust
}
if yoffset >= total {
idx = len(m.lines)
}
return total, idx
}
// lineToIndex taking soft wrappign into account, return the real line index
// for the given line.
func (m Model) lineToIndex(y int) int {
_, idx := m.calculateLine(y)
return idx
}
// lineCount taking soft wrapping into account, return the total viewable line
// count (real lines + soft wrapped line).
func (m Model) lineCount() int {
total, _ := m.calculateLine(0)
return total
}
// maxYOffset returns the maximum possible value of the y-offset based on the
// viewport's content and set height.
func (m Model) maxYOffset() int {
return max(0, m.lineCount()-m.Height()+m.Style.GetVerticalFrameSize())
}
// maxXOffset returns the maximum possible value of the x-offset based on the
// viewport's content and set width.
func (m Model) maxXOffset() int {
return max(0, m.longestLineWidth-m.Width())
}
func (m Model) maxWidth() int {
var gutterSize int
if m.LeftGutterFunc != nil {
gutterSize = lipgloss.Width(m.LeftGutterFunc(GutterContext{}))
}
return m.Width() -
m.Style.GetHorizontalFrameSize() -
gutterSize
}
func (m Model) maxHeight() int {
return m.Height() - m.Style.GetVerticalFrameSize()
}
// visibleLines returns the lines that should currently be visible in the
// viewport.
func (m Model) visibleLines() (lines []string) {
maxHeight := m.maxHeight()
maxWidth := m.maxWidth()
if m.lineCount() > 0 {
pos := m.lineToIndex(m.YOffset)
top := max(0, pos)
bottom := clamp(pos+maxHeight, top, len(m.lines))
lines = make([]string, bottom-top)
copy(lines, m.lines[top:bottom])
lines = m.styleLines(lines, top)
lines = m.highlightLines(lines, top)
}
for m.FillHeight && len(lines) < maxHeight {
lines = append(lines, "")
}
// if longest line fit within width, no need to do anything else.
if (m.xOffset == 0 && m.longestLineWidth <= maxWidth) || maxWidth == 0 {
return m.setupGutter(lines)
}
if m.SoftWrap {
return m.softWrap(lines, maxWidth)
}
for i, line := range lines {
sublines := strings.Split(line, "\n") // will only have more than 1 if caller used [Model.SetContentLines].
for j := range sublines {
sublines[j] = ansi.Cut(sublines[j], m.xOffset, m.xOffset+maxWidth)
}
lines[i] = strings.Join(sublines, "\n")
}
return m.setupGutter(lines)
}
// styleLines styles the lines using [Model.StyleLineFunc].
func (m Model) styleLines(lines []string, offset int) []string {
if m.StyleLineFunc == nil {
return lines
}
for i := range lines {
lines[i] = m.StyleLineFunc(i + offset).Render(lines[i])
}
return lines
}
// highlightLines highlights the lines with [Model.HighlightStyle] and
// [Model.SelectedHighlightStyle].
func (m Model) highlightLines(lines []string, offset int) []string {
if len(m.highlights) == 0 {
return lines
}
for i := range lines {
ranges := makeHighlightRanges(
m.highlights,
i+offset,
m.HighlightStyle,
)
lines[i] = lipgloss.StyleRanges(lines[i], ranges...)
if m.hiIdx < 0 {
continue
}
sel := m.highlights[m.hiIdx]
if hi, ok := sel.lines[i+offset]; ok {
lines[i] = lipgloss.StyleRanges(lines[i], lipgloss.NewRange(
hi[0],
hi[1],
m.SelectedHighlightStyle,
))
}
}
return lines
}
func (m Model) softWrap(lines []string, maxWidth int) []string {
var wrappedLines []string
total := m.TotalLineCount()
for i, line := range lines {
idx := 0
for ansi.StringWidth(line) >= idx {
truncatedLine := ansi.Cut(line, idx, maxWidth+idx)
if m.LeftGutterFunc != nil {
truncatedLine = m.LeftGutterFunc(GutterContext{
Index: i + m.YOffset,
TotalLines: total,
Soft: idx > 0,
}) + truncatedLine
}
wrappedLines = append(wrappedLines, truncatedLine)
idx += maxWidth
}
}
return wrappedLines
}
// setupGutter sets up the left gutter using [Moddel.LeftGutterFunc].
func (m Model) setupGutter(lines []string) []string {
if m.LeftGutterFunc == nil {
return lines
}
offset := max(0, m.lineToIndex(m.YOffset))
total := m.TotalLineCount()
result := make([]string, len(lines))
for i := range lines {
var line []string
for j, realLine := range strings.Split(lines[i], "\n") {
line = append(line, m.LeftGutterFunc(GutterContext{
Index: i + offset,
TotalLines: total,
Soft: j > 0,
})+realLine)
}
result[i] = strings.Join(line, "\n")
}
m.memo.Invalidate()
return result
}
// SetYOffset sets the Y offset.
func (m *Model) SetYOffset(n int) {
m.YOffset = clamp(n, 0, m.maxYOffset())
m.memo.Invalidate()
}
// SetXOffset sets the X offset.
// No-op when soft wrap is enabled.
func (m *Model) SetXOffset(n int) {
if m.SoftWrap {
return
}
m.xOffset = clamp(n, 0, m.maxXOffset())
m.memo.Invalidate()
}
// EnsureVisible ensures that the given line and column are in the viewport.
func (m *Model) EnsureVisible(line, colstart, colend int) {
maxWidth := m.maxWidth()
if colend <= maxWidth {
m.SetXOffset(0)
} else {
m.SetXOffset(colstart - m.horizontalStep) // put one step to the left, feels more natural
}
if line < m.YOffset || line >= m.YOffset+m.maxHeight() {
m.SetYOffset(line)
}
m.visibleLines()
}
// ViewDown moves the view down by the number of lines in the viewport.
// Basically, "page down".
func (m *Model) ViewDown() {
if m.AtBottom() {
return
}
m.LineDown(m.Height())
m.memo.Invalidate()
}
// ViewUp moves the view up by one height of the viewport. Basically, "page up".
func (m *Model) ViewUp() {
if m.AtTop() {
return
}
m.LineUp(m.Height())
m.memo.Invalidate()
}
// HalfViewDown moves the view down by half the height of the viewport.
func (m *Model) HalfViewDown() {
if m.AtBottom() {
return
}
m.LineDown(m.Height() / 2) //nolint:mnd
m.memo.Invalidate()
}
// HalfViewUp moves the view up by half the height of the viewport.
func (m *Model) HalfViewUp() {
if m.AtTop() {
return
}
m.LineUp(m.Height() / 2) //nolint:mnd
m.memo.Invalidate()
}
// LineDown moves the view down by the given number of lines.
func (m *Model) LineDown(n int) {
if m.AtBottom() || n == 0 || len(m.lines) == 0 {
return
}
// Make sure the number of lines by which we're going to scroll isn't
// greater than the number of lines we actually have left before we reach
// the bottom.
m.SetYOffset(m.YOffset + n)
m.hiIdx = m.findNearedtMatch()
m.memo.Invalidate()
}
// LineUp moves the view down by the given number of lines. Returns the new
// lines to show.
func (m *Model) LineUp(n int) {
if m.AtTop() || n == 0 || len(m.lines) == 0 {
return
}
// Make sure the number of lines by which we're going to scroll isn't
// greater than the number of lines we are from the top.
m.SetYOffset(m.YOffset - n)
m.hiIdx = m.findNearedtMatch()
m.memo.Invalidate()
}
// TotalLineCount returns the total number of lines (both hidden and visible) within the viewport.
func (m Model) TotalLineCount() int {
return m.lineCount()
}
// VisibleLineCount returns the number of the visible lines within the viewport.
func (m Model) VisibleLineCount() int {
return len(m.visibleLines())
}
// GotoTop sets the viewport to the top position.
func (m *Model) GotoTop() (lines []string) {
if m.AtTop() {
return nil
}
m.SetYOffset(0)
m.hiIdx = m.findNearedtMatch()
m.memo.Invalidate()
return m.visibleLines()
}
// GotoBottom sets the viewport to the bottom position.
func (m *Model) GotoBottom() (lines []string) {
m.SetYOffset(m.maxYOffset())
m.hiIdx = m.findNearedtMatch()
m.memo.Invalidate()
return m.visibleLines()
}
// SetHorizontalStep sets the amount of cells that the viewport moves in the
// default viewport keymapping. If set to 0 or less, horizontal scrolling is
// disabled.
func (m *Model) SetHorizontalStep(n int) {
if n < 0 {
n = 0
}
m.horizontalStep = n
m.memo.Invalidate()
}
// MoveLeft moves the viewport to the left by the given number of columns.
func (m *Model) MoveLeft(cols int) {
m.xOffset -= cols
if m.xOffset < 0 {
m.xOffset = 0
m.memo.Invalidate()
}
}
// MoveRight moves viewport to the right by the given number of columns.
func (m *Model) MoveRight(cols int) {
// prevents over scrolling to the right
w := m.maxWidth()
if m.xOffset > m.longestLineWidth-w {
return
}
m.xOffset += cols
}
// Resets lines indent to zero.
func (m *Model) ResetIndent() {
m.xOffset = 0
m.memo.Invalidate()
}
// SetHighlights sets ranges of characters to highlight.
// For instance, `[]int{[]int{2, 10}, []int{20, 30}}` will highlight characters
// 2 to 10 and 20 to 30.
// Note that highlights are not expected to transpose each other, and are also
// expected to be in order.
// Use [Model.SetHighlights] to set the highlight ranges, and
// [Model.HighlightNext] and [Model.HighlightPrevious] to navigate.
// Use [Model.ClearHighlights] to remove all highlights.
func (m *Model) SetHighlights(matches [][]int) {
if len(matches) == 0 || len(m.lines) == 0 {
return
}
m.highlights = parseMatches(m.GetContent(), matches)
m.hiIdx = m.findNearedtMatch()
m.showHighlight()
m.memo.Invalidate()
}
// ClearHighlights clears previously set highlights.
func (m *Model) ClearHighlights() {
m.highlights = nil
m.hiIdx = -1
m.memo.Invalidate()
}
func (m *Model) showHighlight() {
if m.hiIdx == -1 {
return
}
line, colstart, colend := m.highlights[m.hiIdx].coords()
m.EnsureVisible(line, colstart, colend)
m.memo.Invalidate()
}
// HighlightNext highlights the next match.
func (m *Model) HighlightNext() {
if m.highlights == nil {
return
}
m.hiIdx = (m.hiIdx + 1) % len(m.highlights)
m.showHighlight()
m.memo.Invalidate()
}
// HighlightPrevious highlights the previous match.
func (m *Model) HighlightPrevious() {
if m.highlights == nil {
return
}
m.hiIdx = (m.hiIdx - 1 + len(m.highlights)) % len(m.highlights)
m.showHighlight()
m.memo.Invalidate()
}
func (m Model) findNearedtMatch() int {
for i, match := range m.highlights {
if match.lineStart >= m.YOffset {
return i
}
}
return -1
}
// Update handles standard message-based viewport updates.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
m = m.updateAsModel(msg)
return m, nil
}
// Author's note: this method has been broken out to make it easier to
// potentially transition Update to satisfy tea.Model.
func (m Model) updateAsModel(msg tea.Msg) Model {
if !m.initialized {
m.setInitialValues()
}
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch {
case key.Matches(msg, m.KeyMap.PageDown):
m.ViewDown()
case key.Matches(msg, m.KeyMap.PageUp):
m.ViewUp()
case key.Matches(msg, m.KeyMap.HalfPageDown):
m.HalfViewDown()
case key.Matches(msg, m.KeyMap.HalfPageUp):
m.HalfViewUp()
case key.Matches(msg, m.KeyMap.Down):
m.LineDown(1)
case key.Matches(msg, m.KeyMap.Up):
m.LineUp(1)
case key.Matches(msg, m.KeyMap.Left):
m.MoveLeft(m.horizontalStep)
case key.Matches(msg, m.KeyMap.Right):
m.MoveRight(m.horizontalStep)
}
case tea.MouseWheelMsg:
if !m.MouseWheelEnabled {
break
}
switch msg.Button {
case tea.MouseWheelDown:
m.LineDown(m.MouseWheelDelta)
case tea.MouseWheelUp:
m.LineUp(m.MouseWheelDelta)
}
}
return m
}
// View renders the viewport into a string.
func (m *Model) render() {
}
func (m Model) View() string {
return m.memo.View(func() string {
w, h := m.Width(), m.Height()
if sw := m.Style.GetWidth(); sw != 0 {
w = min(w, sw)
}
if sh := m.Style.GetHeight(); sh != 0 {
h = min(h, sh)
}
contentWidth := w - m.Style.GetHorizontalFrameSize()
contentHeight := h - m.Style.GetVerticalFrameSize()
visible := m.visibleLines()
contents := lipgloss.NewStyle().
Width(contentWidth). // pad to width.
Height(contentHeight). // pad to height.
MaxHeight(contentHeight). // truncate height if taller.
MaxWidth(contentWidth). // truncate width if wider.
Render(strings.Join(visible, "\n"))
return m.Style.
UnsetWidth().UnsetHeight(). // Style size already applied in contents.
Render(contents)
})
}
func clamp(v, low, high int) int {
if high < low {
low, high = high, low
}
return min(high, max(low, v))
}
func maxLineWidth(lines []string) int {
result := 0
for _, line := range lines {
result = max(result, lipgloss.Width(line))
}
return result
}

View File

@@ -1,4 +1,4 @@
configured_endpoints: 22
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-d34620b462127c45497743c97fd3569f9e629d9fbd97c0707087eeddbd4b3de1.yml
openapi_spec_hash: 23864c98d555350fe56f1d0e56f205c5
config_hash: a8441af7cb2db855d79fd372ee3b9fb1
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-e7f4ac9b5afd5c6db4741a27b5445167808b0a3b7c36dfd525bfb3446a11a253.yml
openapi_spec_hash: 3e7b367a173d6de7924f35a41ac6b5a5
config_hash: 6d56a7ca0d6ed899ecdb5c053a8278ae

View File

@@ -64,7 +64,6 @@ Response Types:
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Config">Config</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#KeybindsConfig">KeybindsConfig</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#LayoutConfig">LayoutConfig</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#McpLocalConfig">McpLocalConfig</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#McpRemoteConfig">McpRemoteConfig</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ModeConfig">ModeConfig</a>
@@ -78,18 +77,26 @@ Methods:
Params Types:
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartInputParam">FilePartInputParam</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartSourceUnionParam">FilePartSourceUnionParam</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartSourceTextParam">FilePartSourceTextParam</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileSourceParam">FileSourceParam</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SymbolSourceParam">SymbolSourceParam</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TextPartInputParam">TextPartInputParam</a>
Response Types:
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePart">FilePart</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartSource">FilePartSource</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartSourceText">FilePartSourceText</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FileSource">FileSource</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Message">Message</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Part">Part</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SnapshotPart">SnapshotPart</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#StepFinishPart">StepFinishPart</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#StepStartPart">StepStartPart</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SymbolSource">SymbolSource</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TextPart">TextPart</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolPart">ToolPart</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStateCompleted">ToolStateCompleted</a>

View File

@@ -55,8 +55,8 @@ type Config struct {
Instructions []string `json:"instructions"`
// Custom keybind configurations
Keybinds KeybindsConfig `json:"keybinds"`
// Layout to use for the TUI
Layout LayoutConfig `json:"layout"`
// @deprecated Always uses stretch layout.
Layout ConfigLayout `json:"layout"`
// Minimum log level to write to log files
LogLevel LogLevel `json:"log_level"`
// MCP (Model Context Protocol) server configurations
@@ -70,6 +70,9 @@ type Config struct {
// Control sharing behavior:'manual' allows manual sharing via commands, 'auto'
// enables automatic sharing, 'disabled' disables all sharing
Share ConfigShare `json:"share"`
// Small model to use for tasks like summarization and title generation in the
// format of provider/model
SmallModel string `json:"small_model"`
// Theme name to use for the interface
Theme string `json:"theme"`
// Custom username to display in conversations instead of system username
@@ -93,6 +96,7 @@ type configJSON struct {
Model apijson.Field
Provider apijson.Field
Share apijson.Field
SmallModel apijson.Field
Theme apijson.Field
Username apijson.Field
raw string
@@ -197,6 +201,22 @@ func (r configExperimentalHookSessionCompletedJSON) RawJSON() string {
return r.raw
}
// @deprecated Always uses stretch layout.
type ConfigLayout string
const (
ConfigLayoutAuto ConfigLayout = "auto"
ConfigLayoutStretch ConfigLayout = "stretch"
)
func (r ConfigLayout) IsKnown() bool {
switch r {
case ConfigLayoutAuto, ConfigLayoutStretch:
return true
}
return false
}
type ConfigMcp struct {
// Type of MCP server connection
Type ConfigMcpType `json:"type,required"`
@@ -574,21 +594,6 @@ func (r keybindsConfigJSON) RawJSON() string {
return r.raw
}
type LayoutConfig string
const (
LayoutConfigAuto LayoutConfig = "auto"
LayoutConfigStretch LayoutConfig = "stretch"
)
func (r LayoutConfig) IsKnown() bool {
switch r {
case LayoutConfigAuto, LayoutConfigStretch:
return true
}
return false
}
type McpLocalConfig struct {
// Command and arguments to run the MCP server
Command []string `json:"command,required"`

View File

@@ -434,14 +434,15 @@ func (r AssistantMessageErrorName) IsKnown() bool {
}
type FilePart struct {
ID string `json:"id,required"`
MessageID string `json:"messageID,required"`
Mime string `json:"mime,required"`
SessionID string `json:"sessionID,required"`
Type FilePartType `json:"type,required"`
URL string `json:"url,required"`
Filename string `json:"filename"`
JSON filePartJSON `json:"-"`
ID string `json:"id,required"`
MessageID string `json:"messageID,required"`
Mime string `json:"mime,required"`
SessionID string `json:"sessionID,required"`
Type FilePartType `json:"type,required"`
URL string `json:"url,required"`
Filename string `json:"filename"`
Source FilePartSource `json:"source"`
JSON filePartJSON `json:"-"`
}
// filePartJSON contains the JSON metadata for the struct [FilePart]
@@ -453,6 +454,7 @@ type filePartJSON struct {
Type apijson.Field
URL apijson.Field
Filename apijson.Field
Source apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
@@ -482,11 +484,12 @@ func (r FilePartType) IsKnown() bool {
}
type FilePartInputParam struct {
Mime param.Field[string] `json:"mime,required"`
Type param.Field[FilePartInputType] `json:"type,required"`
URL param.Field[string] `json:"url,required"`
ID param.Field[string] `json:"id"`
Filename param.Field[string] `json:"filename"`
Mime param.Field[string] `json:"mime,required"`
Type param.Field[FilePartInputType] `json:"type,required"`
URL param.Field[string] `json:"url,required"`
ID param.Field[string] `json:"id"`
Filename param.Field[string] `json:"filename"`
Source param.Field[FilePartSourceUnionParam] `json:"source"`
}
func (r FilePartInputParam) MarshalJSON() (data []byte, err error) {
@@ -509,6 +512,195 @@ func (r FilePartInputType) IsKnown() bool {
return false
}
type FilePartSource struct {
Path string `json:"path,required"`
Text FilePartSourceText `json:"text,required"`
Type FilePartSourceType `json:"type,required"`
Kind int64 `json:"kind"`
Name string `json:"name"`
// This field can have the runtime type of [SymbolSourceRange].
Range interface{} `json:"range"`
JSON filePartSourceJSON `json:"-"`
union FilePartSourceUnion
}
// filePartSourceJSON contains the JSON metadata for the struct [FilePartSource]
type filePartSourceJSON struct {
Path apijson.Field
Text apijson.Field
Type apijson.Field
Kind apijson.Field
Name apijson.Field
Range apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r filePartSourceJSON) RawJSON() string {
return r.raw
}
func (r *FilePartSource) UnmarshalJSON(data []byte) (err error) {
*r = FilePartSource{}
err = apijson.UnmarshalRoot(data, &r.union)
if err != nil {
return err
}
return apijson.Port(r.union, &r)
}
// AsUnion returns a [FilePartSourceUnion] interface which you can cast to the
// specific types for more type safety.
//
// Possible runtime types of the union are [FileSource], [SymbolSource].
func (r FilePartSource) AsUnion() FilePartSourceUnion {
return r.union
}
// Union satisfied by [FileSource] or [SymbolSource].
type FilePartSourceUnion interface {
implementsFilePartSource()
}
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*FilePartSourceUnion)(nil)).Elem(),
"type",
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(FileSource{}),
DiscriminatorValue: "file",
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(SymbolSource{}),
DiscriminatorValue: "symbol",
},
)
}
type FilePartSourceType string
const (
FilePartSourceTypeFile FilePartSourceType = "file"
FilePartSourceTypeSymbol FilePartSourceType = "symbol"
)
func (r FilePartSourceType) IsKnown() bool {
switch r {
case FilePartSourceTypeFile, FilePartSourceTypeSymbol:
return true
}
return false
}
type FilePartSourceParam struct {
Path param.Field[string] `json:"path,required"`
Text param.Field[FilePartSourceTextParam] `json:"text,required"`
Type param.Field[FilePartSourceType] `json:"type,required"`
Kind param.Field[int64] `json:"kind"`
Name param.Field[string] `json:"name"`
Range param.Field[interface{}] `json:"range"`
}
func (r FilePartSourceParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
func (r FilePartSourceParam) implementsFilePartSourceUnionParam() {}
// Satisfied by [FileSourceParam], [SymbolSourceParam], [FilePartSourceParam].
type FilePartSourceUnionParam interface {
implementsFilePartSourceUnionParam()
}
type FilePartSourceText struct {
End int64 `json:"end,required"`
Start int64 `json:"start,required"`
Value string `json:"value,required"`
JSON filePartSourceTextJSON `json:"-"`
}
// filePartSourceTextJSON contains the JSON metadata for the struct
// [FilePartSourceText]
type filePartSourceTextJSON struct {
End apijson.Field
Start apijson.Field
Value apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *FilePartSourceText) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r filePartSourceTextJSON) RawJSON() string {
return r.raw
}
type FilePartSourceTextParam struct {
End param.Field[int64] `json:"end,required"`
Start param.Field[int64] `json:"start,required"`
Value param.Field[string] `json:"value,required"`
}
func (r FilePartSourceTextParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
type FileSource struct {
Path string `json:"path,required"`
Text FilePartSourceText `json:"text,required"`
Type FileSourceType `json:"type,required"`
JSON fileSourceJSON `json:"-"`
}
// fileSourceJSON contains the JSON metadata for the struct [FileSource]
type fileSourceJSON struct {
Path apijson.Field
Text apijson.Field
Type apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *FileSource) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r fileSourceJSON) RawJSON() string {
return r.raw
}
func (r FileSource) implementsFilePartSource() {}
type FileSourceType string
const (
FileSourceTypeFile FileSourceType = "file"
)
func (r FileSourceType) IsKnown() bool {
switch r {
case FileSourceTypeFile:
return true
}
return false
}
type FileSourceParam struct {
Path param.Field[string] `json:"path,required"`
Text param.Field[FilePartSourceTextParam] `json:"text,required"`
Type param.Field[FileSourceType] `json:"type,required"`
}
func (r FileSourceParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
func (r FileSourceParam) implementsFilePartSourceUnionParam() {}
type Message struct {
ID string `json:"id,required"`
Role MessageRole `json:"role,required"`
@@ -609,15 +801,16 @@ func (r MessageRole) IsKnown() bool {
}
type Part struct {
ID string `json:"id,required"`
MessageID string `json:"messageID,required"`
SessionID string `json:"sessionID,required"`
Type PartType `json:"type,required"`
CallID string `json:"callID"`
Cost float64 `json:"cost"`
Filename string `json:"filename"`
Mime string `json:"mime"`
Snapshot string `json:"snapshot"`
ID string `json:"id,required"`
MessageID string `json:"messageID,required"`
SessionID string `json:"sessionID,required"`
Type PartType `json:"type,required"`
CallID string `json:"callID"`
Cost float64 `json:"cost"`
Filename string `json:"filename"`
Mime string `json:"mime"`
Snapshot string `json:"snapshot"`
Source FilePartSource `json:"source"`
// This field can have the runtime type of [ToolPartState].
State interface{} `json:"state"`
Synthetic bool `json:"synthetic"`
@@ -643,6 +836,7 @@ type partJSON struct {
Filename apijson.Field
Mime apijson.Field
Snapshot apijson.Field
Source apijson.Field
State apijson.Field
Synthetic apijson.Field
Text apijson.Field
@@ -1018,6 +1212,163 @@ func (r StepStartPartType) IsKnown() bool {
return false
}
type SymbolSource struct {
Kind int64 `json:"kind,required"`
Name string `json:"name,required"`
Path string `json:"path,required"`
Range SymbolSourceRange `json:"range,required"`
Text FilePartSourceText `json:"text,required"`
Type SymbolSourceType `json:"type,required"`
JSON symbolSourceJSON `json:"-"`
}
// symbolSourceJSON contains the JSON metadata for the struct [SymbolSource]
type symbolSourceJSON struct {
Kind apijson.Field
Name apijson.Field
Path apijson.Field
Range apijson.Field
Text apijson.Field
Type apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *SymbolSource) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r symbolSourceJSON) RawJSON() string {
return r.raw
}
func (r SymbolSource) implementsFilePartSource() {}
type SymbolSourceRange struct {
End SymbolSourceRangeEnd `json:"end,required"`
Start SymbolSourceRangeStart `json:"start,required"`
JSON symbolSourceRangeJSON `json:"-"`
}
// symbolSourceRangeJSON contains the JSON metadata for the struct
// [SymbolSourceRange]
type symbolSourceRangeJSON struct {
End apijson.Field
Start apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *SymbolSourceRange) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r symbolSourceRangeJSON) RawJSON() string {
return r.raw
}
type SymbolSourceRangeEnd struct {
Character float64 `json:"character,required"`
Line float64 `json:"line,required"`
JSON symbolSourceRangeEndJSON `json:"-"`
}
// symbolSourceRangeEndJSON contains the JSON metadata for the struct
// [SymbolSourceRangeEnd]
type symbolSourceRangeEndJSON struct {
Character apijson.Field
Line apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *SymbolSourceRangeEnd) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r symbolSourceRangeEndJSON) RawJSON() string {
return r.raw
}
type SymbolSourceRangeStart struct {
Character float64 `json:"character,required"`
Line float64 `json:"line,required"`
JSON symbolSourceRangeStartJSON `json:"-"`
}
// symbolSourceRangeStartJSON contains the JSON metadata for the struct
// [SymbolSourceRangeStart]
type symbolSourceRangeStartJSON struct {
Character apijson.Field
Line apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *SymbolSourceRangeStart) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r symbolSourceRangeStartJSON) RawJSON() string {
return r.raw
}
type SymbolSourceType string
const (
SymbolSourceTypeSymbol SymbolSourceType = "symbol"
)
func (r SymbolSourceType) IsKnown() bool {
switch r {
case SymbolSourceTypeSymbol:
return true
}
return false
}
type SymbolSourceParam struct {
Kind param.Field[int64] `json:"kind,required"`
Name param.Field[string] `json:"name,required"`
Path param.Field[string] `json:"path,required"`
Range param.Field[SymbolSourceRangeParam] `json:"range,required"`
Text param.Field[FilePartSourceTextParam] `json:"text,required"`
Type param.Field[SymbolSourceType] `json:"type,required"`
}
func (r SymbolSourceParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
func (r SymbolSourceParam) implementsFilePartSourceUnionParam() {}
type SymbolSourceRangeParam struct {
End param.Field[SymbolSourceRangeEndParam] `json:"end,required"`
Start param.Field[SymbolSourceRangeStartParam] `json:"start,required"`
}
func (r SymbolSourceRangeParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
type SymbolSourceRangeEndParam struct {
Character param.Field[float64] `json:"character,required"`
Line param.Field[float64] `json:"line,required"`
}
func (r SymbolSourceRangeEndParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
type SymbolSourceRangeStartParam struct {
Character param.Field[float64] `json:"character,required"`
Line param.Field[float64] `json:"line,required"`
}
func (r SymbolSourceRangeStartParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
type TextPart struct {
ID string `json:"id,required"`
MessageID string `json:"messageID,required"`
@@ -1614,6 +1965,7 @@ type SessionChatParamsPart struct {
ID param.Field[string] `json:"id"`
Filename param.Field[string] `json:"filename"`
Mime param.Field[string] `json:"mime"`
Source param.Field[FilePartSourceUnionParam] `json:"source"`
Synthetic param.Field[bool] `json:"synthetic"`
Text param.Field[string] `json:"text"`
Time param.Field[interface{}] `json:"time"`

View File

@@ -18,19 +18,24 @@ You can define MCP servers in your opencode config under `mcp`.
### Local
Add a local MCP servers under `mcp.localmcp`.
Add local MCP servers under `mcp` with `"type": "local"`.
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"localmcp": {
"my-local-mcp-server": {
"type": "local",
"command": ["bun", "x", "my-mcp-command"],
"enabled": true,
"environment": {
"MY_ENV_VAR": "my_env_var_value"
}
}, {
"my-different-local-mcp-server": {
"type": "local",
"command": ["bun", "x", "my-other-mcp-command"],
"enabled": true
}
}
}
@@ -40,13 +45,13 @@ You can also disable a server by setting `enabled` to `false`. This is useful if
### Remote
Add a remote MCP servers under `mcp.remotemcp`.
Add remote MCP servers under `mcp` with `"type": "remote"`.
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"remotemcp": {
"my-remote-mcp": {
"type": "remote",
"url": "https://my-mcp-server.com",
"enabled": true,

View File

@@ -61,6 +61,49 @@ You can customize the base URL for any provider by setting the `baseURL` option.
---
### OpenRouter
Many OpenRouter models are preloaded by default - you can customize these or add your own.
Here's an example of specifying a provider
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"openrouter": {
"models": {
"moonshotai/kimi-k2": {
"options": {
"provider": {
"order": ["baseten"],
"allow_fallbacks": false
}
}
}
}
}
}
}
```
You can also add additional models
```json title="opencode.json"
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"openrouter": {
"models": {
"somecoolnewmodel": {},
}
}
}
}
```
---
### Local
You can configure local model like ones served through LM Studio or Ollama. To

View File

@@ -1,16 +0,0 @@
#!/usr/bin/env bun
import { $ } from "bun"
try {
await $`git tag -d github-v1`
await $`git push origin :refs/tags/github-v1`
} catch (e: any) {
if (e instanceof $.ShellError && e.stderr.toString().match(/tag \S+ not found/)) {
console.log("tag not found, continuing...")
} else {
throw e
}
}
await $`git tag -a github-v1 -m "Update github-v1 to latest"`
await $`git push origin github-v1`

View File

@@ -12,7 +12,7 @@ done
git fetch --force --tags
# Get the latest Git tag
latest_tag=$(git tag --sort=committerdate | grep -E '[0-9]' | tail -1)
latest_tag=$(git tag --sort=committerdate | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | tail -1)
# If there is no tag, exit the script
if [ -z "$latest_tag" ]; then

View File

@@ -2,6 +2,14 @@
set -e
# Parse command line arguments
DEV_MODE=false
for arg in "$@"; do
if [ "$arg" = "--dev" ]; then
DEV_MODE=true
fi
done
echo "Starting opencode server on port 4096..."
bun run ./packages/opencode/src/index.ts serve --port 4096 &
SERVER_PID=$!
@@ -23,7 +31,13 @@ rm -rf packages/tui/sdk
mv opencode-go/ packages/tui/sdk/
rm -rf packages/tui/sdk/.git
echo "Kicking off production build..."
stl builds create --branch main --wait false
# Only run production build if not in dev mode
if [ "$DEV_MODE" = false ]; then
echo "Kicking off production build..."
stl builds create --branch main --wait=false
else
echo "Skipping production build (--dev flag detected)"
fi
echo "Done!"

15
sdks/github/script/publish Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Get the latest Git tag
latest_tag=$(git tag --sort=committerdate | grep -E '^github-v[0-9]+\.[0-9]+\.[0-9]+$' | tail -1)
if [ -z "$latest_tag" ]; then
echo "No tags found"
exit 1
fi
echo "Latest tag: $latest_tag"
# Update github-v1 to latest
git tag -d github-v1
git push origin :refs/tags/github-v1
git tag -a github-v1 $latest_tag -m "Update github-v1 to $latest_tag"
git push origin github-v1

41
sdks/github/script/release Executable file
View File

@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Parse command line arguments
minor=false
while [ "$#" -gt 0 ]; do
case "$1" in
--minor) minor=true; shift 1;;
*) echo "Unknown parameter: $1"; exit 1;;
esac
done
# Get the latest Git tag
git fetch --force --tags
latest_tag=$(git tag --sort=committerdate | grep -E '^github-v[0-9]+\.[0-9]+\.[0-9]+$' | tail -1)
if [ -z "$latest_tag" ]; then
echo "No tags found"
exit 1
fi
echo "Latest tag: $latest_tag"
# Split the tag into major, minor, and patch numbers
IFS='.' read -ra VERSION <<< "$latest_tag"
if [ "$minor" = true ]; then
# Increment the minor version and reset patch to 0
minor_number=${VERSION[1]}
let "minor_number++"
new_version="${VERSION[0]}.$minor_number.0"
else
# Increment the patch version
patch_number=${VERSION[2]}
let "patch_number++"
new_version="${VERSION[0]}.${VERSION[1]}.$patch_number"
fi
echo "New version: $new_version"
# Tag
git tag $new_version
git push --tags

1
sdks/vscode/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dist

View File

@@ -0,0 +1,5 @@
import { defineConfig } from '@vscode/test-cli';
export default defineConfig({
files: 'out/test/**/*.test.js',
});

16
sdks/vscode/.vscodeignore Normal file
View File

@@ -0,0 +1,16 @@
.vscode/**
.vscode-test/**
out/**
node_modules/**
src/**
script/**
.gitignore
.yarnrc
bun.lock
esbuild.js
vsc-extension-quickstart.md
**/tsconfig.json
**/eslint.config.mjs
**/*.map
**/*.ts
**/.vscode-test.*

17
sdks/vscode/README.md Normal file
View File

@@ -0,0 +1,17 @@
# opencode VS Code Extension
A VS Code extension that integrates [opencode](https://opencode.ai) directly into your development environment.
## Prerequisites
This extension requires [opencode](https://opencode.ai) to be installed on your system. Visit [opencode.ai](https://opencode.ai) for installation instructions.
## Features
- **Cmd+Escape**: Launch opencode in a split terminal view
- **Alt+Cmd+K**: Send selected code to opencode's prompt
- **Tab awareness**: opencode automatically detects which files you have open
## Support
This is an early release. If you encounter issues or have feedback, please create an issue at https://github.com/sst/opencode/issues.

589
sdks/vscode/bun.lock Normal file
View File

@@ -0,0 +1,589 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "opencode-agent",
"devDependencies": {
"@types/mocha": "^10.0.10",
"@types/node": "20.x",
"@types/vscode": "^1.102.0",
"@typescript-eslint/eslint-plugin": "^8.31.1",
"@typescript-eslint/parser": "^8.31.1",
"@vscode/test-cli": "^0.0.11",
"@vscode/test-electron": "^2.5.2",
"esbuild": "^0.25.3",
"eslint": "^9.25.1",
"typescript": "^5.8.3",
},
},
},
"packages": {
"@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.8", "", { "os": "aix", "cpu": "ppc64" }, "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.8", "", { "os": "android", "cpu": "arm" }, "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.8", "", { "os": "android", "cpu": "arm64" }, "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.8", "", { "os": "android", "cpu": "x64" }, "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.8", "", { "os": "linux", "cpu": "arm" }, "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.8", "", { "os": "linux", "cpu": "ia32" }, "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.8", "", { "os": "linux", "cpu": "none" }, "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.8", "", { "os": "linux", "cpu": "x64" }, "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.8", "", { "os": "none", "cpu": "x64" }, "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.8", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.8", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.8", "", { "os": "none", "cpu": "arm64" }, "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.8", "", { "os": "sunos", "cpu": "x64" }, "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.8", "", { "os": "win32", "cpu": "x64" }, "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw=="],
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.7.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw=="],
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
"@eslint/config-array": ["@eslint/config-array@0.21.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ=="],
"@eslint/config-helpers": ["@eslint/config-helpers@0.3.0", "", {}, "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw=="],
"@eslint/core": ["@eslint/core@0.15.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA=="],
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
"@eslint/js": ["@eslint/js@9.31.0", "", {}, "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw=="],
"@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.3", "", { "dependencies": { "@eslint/core": "^0.15.1", "levn": "^0.4.1" } }, "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
"@humanfs/node": ["@humanfs/node@0.16.6", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.3.0" } }, "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw=="],
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
"@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=="],
"@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
"@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=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/mocha": ["@types/mocha@10.0.10", "", {}, "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q=="],
"@types/node": ["@types/node@20.19.9", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw=="],
"@types/vscode": ["@types/vscode@1.102.0", "", {}, "sha512-V9sFXmcXz03FtYTSUsYsu5K0Q9wH9w9V25slddcxrh5JgORD14LpnOA7ov0L9ALi+6HrTjskLJ/tY5zeRF3TFA=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.37.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.37.0", "@typescript-eslint/type-utils": "8.37.0", "@typescript-eslint/utils": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.37.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.37.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.37.0", "@typescript-eslint/types": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA=="],
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.37.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.37.0", "@typescript-eslint/types": "^8.37.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA=="],
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0" } }, "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA=="],
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.37.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg=="],
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0", "@typescript-eslint/utils": "8.37.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.37.0", "", {}, "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ=="],
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.37.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.37.0", "@typescript-eslint/tsconfig-utils": "8.37.0", "@typescript-eslint/types": "8.37.0", "@typescript-eslint/visitor-keys": "8.37.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.9.0" } }, "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg=="],
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.37.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.37.0", "@typescript-eslint/types": "8.37.0", "@typescript-eslint/typescript-estree": "8.37.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A=="],
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.37.0", "", { "dependencies": { "@typescript-eslint/types": "8.37.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w=="],
"@vscode/test-cli": ["@vscode/test-cli@0.0.11", "", { "dependencies": { "@types/mocha": "^10.0.2", "c8": "^9.1.0", "chokidar": "^3.5.3", "enhanced-resolve": "^5.15.0", "glob": "^10.3.10", "minimatch": "^9.0.3", "mocha": "^11.1.0", "supports-color": "^9.4.0", "yargs": "^17.7.2" }, "bin": { "vscode-test": "out/bin.mjs" } }, "sha512-qO332yvzFqGhBMJrp6TdwbIydiHgCtxXc2Nl6M58mbH/Z+0CyLR76Jzv4YWPEthhrARprzCRJUqzFvTHFhTj7Q=="],
"@vscode/test-electron": ["@vscode/test-electron@2.5.2", "", { "dependencies": { "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.5", "jszip": "^3.10.1", "ora": "^8.1.0", "semver": "^7.6.2" } }, "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
"ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"browser-stdout": ["browser-stdout@1.3.1", "", {}, "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw=="],
"c8": ["c8@9.1.0", "", { "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@istanbuljs/schema": "^0.1.3", "find-up": "^5.0.0", "foreground-child": "^3.1.1", "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.1.6", "test-exclude": "^6.0.0", "v8-to-istanbul": "^9.0.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1" }, "bin": { "c8": "bin/c8.js" } }, "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg=="],
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
"cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
"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=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"decamelize": ["decamelize@4.0.0", "", {}, "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
"diff": ["diff@7.0.0", "", {}, "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw=="],
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"enhanced-resolve": ["enhanced-resolve@5.18.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ=="],
"esbuild": ["esbuild@0.25.8", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.8", "@esbuild/android-arm": "0.25.8", "@esbuild/android-arm64": "0.25.8", "@esbuild/android-x64": "0.25.8", "@esbuild/darwin-arm64": "0.25.8", "@esbuild/darwin-x64": "0.25.8", "@esbuild/freebsd-arm64": "0.25.8", "@esbuild/freebsd-x64": "0.25.8", "@esbuild/linux-arm": "0.25.8", "@esbuild/linux-arm64": "0.25.8", "@esbuild/linux-ia32": "0.25.8", "@esbuild/linux-loong64": "0.25.8", "@esbuild/linux-mips64el": "0.25.8", "@esbuild/linux-ppc64": "0.25.8", "@esbuild/linux-riscv64": "0.25.8", "@esbuild/linux-s390x": "0.25.8", "@esbuild/linux-x64": "0.25.8", "@esbuild/netbsd-arm64": "0.25.8", "@esbuild/netbsd-x64": "0.25.8", "@esbuild/openbsd-arm64": "0.25.8", "@esbuild/openbsd-x64": "0.25.8", "@esbuild/openharmony-arm64": "0.25.8", "@esbuild/sunos-x64": "0.25.8", "@esbuild/win32-arm64": "0.25.8", "@esbuild/win32-ia32": "0.25.8", "@esbuild/win32-x64": "0.25.8" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"eslint": ["eslint@9.31.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.31.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ=="],
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
"flat": ["flat@5.0.2", "", { "bin": { "flat": "cli.js" } }, "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ=="],
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
"get-east-asian-width": ["get-east-asian-width@1.3.0", "", {}, "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ=="],
"glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
"globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
"html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="],
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-plain-obj": ["is-plain-obj@2.1.0", "", {}, "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="],
"is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
"istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="],
"istanbul-reports": ["istanbul-reports@3.1.7", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g=="],
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
"jszip": ["jszip@3.10.1", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
"lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="],
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
"log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
"minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
"mocha": ["mocha@11.7.1", "", { "dependencies": { "browser-stdout": "^1.3.1", "chokidar": "^4.0.1", "debug": "^4.3.5", "diff": "^7.0.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", "glob": "^10.4.5", "he": "^1.2.0", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", "minimatch": "^9.0.5", "ms": "^2.1.3", "picocolors": "^1.1.1", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", "workerpool": "^9.2.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" }, "bin": { "mocha": "bin/mocha.js", "_mocha": "bin/_mocha" } }, "sha512-5EK+Cty6KheMS/YLPPMJC64g5V61gIR25KsRItHw6x4hEKT6Njp1n9LOlH4gpevuwMVS66SXaBBpg+RWZkza4A=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"ora": ["ora@8.2.0", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="],
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
"pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
"path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"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=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="],
"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=="],
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"serialize-javascript": ["serialize-javascript@6.0.2", "", { "dependencies": { "randombytes": "^2.1.0" } }, "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g=="],
"setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="],
"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=="],
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
"strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
"supports-color": ["supports-color@9.4.0", "", {}, "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw=="],
"tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="],
"test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
"workerpool": ["workerpool@9.3.3", "", {}, "sha512-slxCaKbYjEdFT/o2rH9xS1hf4uRDch1w7Uo+apxhZ+sf/1d9e0ZVkn42kPNGP2dgjIx6YFvSevj0zHvbWe2jdw=="],
"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=="],
"wrap-ansi-cjs": ["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=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
"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=="],
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
"yargs-unparser": ["yargs-unparser@2.0.0", "", { "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", "flat": "^5.0.2", "is-plain-obj": "^2.1.0" } }, "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA=="],
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@eslint/config-array/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"@eslint/eslintrc/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
"@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=="],
"@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=="],
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"eslint/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"istanbul-lib-report/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"log-symbols/is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
"mocha/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"mocha/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
"ora/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
"ora/log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="],
"ora/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"test-exclude/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"test-exclude/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
"cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"eslint/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"mocha/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
"ora/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"test-exclude/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
}
}

56
sdks/vscode/esbuild.js Normal file
View File

@@ -0,0 +1,56 @@
const esbuild = require("esbuild");
const production = process.argv.includes('--production');
const watch = process.argv.includes('--watch');
/**
* @type {import('esbuild').Plugin}
*/
const esbuildProblemMatcherPlugin = {
name: 'esbuild-problem-matcher',
setup(build) {
build.onStart(() => {
console.log('[watch] build started');
});
build.onEnd((result) => {
result.errors.forEach(({ text, location }) => {
console.error(`✘ [ERROR] ${text}`);
console.error(` ${location.file}:${location.line}:${location.column}:`);
});
console.log('[watch] build finished');
});
},
};
async function main() {
const ctx = await esbuild.context({
entryPoints: [
'src/extension.ts'
],
bundle: true,
format: 'cjs',
minify: production,
sourcemap: !production,
sourcesContent: false,
platform: 'node',
outfile: 'dist/extension.js',
external: ['vscode'],
logLevel: 'silent',
plugins: [
/* add to the end of plugins array */
esbuildProblemMatcherPlugin,
],
});
if (watch) {
await ctx.watch();
} else {
await ctx.rebuild();
await ctx.dispose();
}
}
main().catch(e => {
console.error(e);
process.exit(1);
});

View File

@@ -0,0 +1,28 @@
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
export default [{
files: ["**/*.ts"],
}, {
plugins: {
"@typescript-eslint": typescriptEslint,
},
languageOptions: {
parser: tsParser,
ecmaVersion: 2022,
sourceType: "module",
},
rules: {
"@typescript-eslint/naming-convention": ["warn", {
selector: "import",
format: ["camelCase", "PascalCase"],
}],
curly: "warn",
eqeqeq: "warn",
"no-throw-literal": "warn",
semi: "warn",
},
}];

BIN
sdks/vscode/images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

76
sdks/vscode/package.json Normal file
View File

@@ -0,0 +1,76 @@
{
"name": "opencode",
"displayName": "opencode",
"description": "opencode for VS Code",
"version": "0.0.0",
"publisher": "sst-dev",
"repository": {
"type": "git",
"url": "https://github.com/sst/opencode"
},
"license": "MIT",
"icon": "images/icon.png",
"galleryBanner": {
"color": "#000000",
"theme": "dark"
},
"engines": {
"vscode": "^1.94.0"
},
"categories": [
"Other"
],
"activationEvents": [],
"main": "./dist/extension.js",
"contributes": {
"commands": [
{
"command": "opencode.openTerminal",
"title": "Open Terminal with Opencode"
},
{
"command": "opencode.addFilepathToTerminal",
"title": "Add Filepath to Terminal"
}
],
"keybindings": [
{
"command": "opencode.openTerminal",
"title": "Run opencode",
"key": "cmd+escape",
"mac": "cmd+escape"
},
{
"command": "opencode.addFilepathToTerminal",
"title": "opencode: Insert At-Mentioned",
"key": "cmd+alt+k",
"mac": "cmd+alt+k"
}
]
},
"scripts": {
"vscode:prepublish": "bun run package",
"compile": "bun run check-types && bun run lint && node esbuild.js",
"watch:esbuild": "node esbuild.js --watch",
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
"package": "bun run check-types && bun run lint && node esbuild.js --production",
"compile-tests": "tsc -p . --outDir out",
"watch-tests": "tsc -p . -w --outDir out",
"pretest": "bun run compile-tests && bun run compile && bun run lint",
"check-types": "tsc --noEmit",
"lint": "eslint src",
"test": "vscode-test"
},
"devDependencies": {
"@types/vscode": "^1.102.0",
"@types/mocha": "^10.0.10",
"@types/node": "20.x",
"@typescript-eslint/eslint-plugin": "^8.31.1",
"@typescript-eslint/parser": "^8.31.1",
"eslint": "^9.25.1",
"esbuild": "^0.25.3",
"typescript": "^5.8.3",
"@vscode/test-cli": "^0.0.11",
"@vscode/test-electron": "^2.5.2"
}
}

20
sdks/vscode/script/publish Executable file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Get the latest Git tag
latest_tag=$(git tag --sort=committerdate | grep -E '^vscode-v[0-9]+\.[0-9]+\.[0-9]+$' | tail -1)
if [ -z "$latest_tag" ]; then
echo "No tags found"
exit 1
fi
echo "Latest tag: $latest_tag"
version=$(echo $latest_tag | sed 's/^vscode-v//')
echo "Latest version: $version"
# package-marketplace
vsce package --no-git-tag-version --no-update-package-json --no-dependencies --skip-license -o dist/opencode.vsix $version
# publish-marketplace
vsce publish --packagePath dist/opencode.vsix
# publish-openvsx
npx ovsx publish dist/opencode.vsix -p $OPENVSX_TOKEN

41
sdks/vscode/script/release Executable file
View File

@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Parse command line arguments
minor=false
while [ "$#" -gt 0 ]; do
case "$1" in
--minor) minor=true; shift 1;;
*) echo "Unknown parameter: $1"; exit 1;;
esac
done
# Get the latest Git tag
git fetch --force --tags
latest_tag=$(git tag --sort=committerdate | grep -E '^vscode-v[0-9]+\.[0-9]+\.[0-9]+$' | tail -1)
if [ -z "$latest_tag" ]; then
echo "No tags found"
exit 1
fi
echo "Latest tag: $latest_tag"
# Split the tag into major, minor, and patch numbers
IFS='.' read -ra VERSION <<< "$latest_tag"
if [ "$minor" = true ]; then
# Increment the minor version and reset patch to 0
minor_number=${VERSION[1]}
let "minor_number++"
new_version="${VERSION[0]}.$minor_number.0"
else
# Increment the patch version
patch_number=${VERSION[2]}
let "patch_number++"
new_version="${VERSION[0]}.${VERSION[1]}.$patch_number"
fi
echo "New version: $new_version"
# Tag
git tag $new_version
git push --tags

View File

@@ -0,0 +1,79 @@
// This method is called when your extension is deactivated
export function deactivate() {}
import * as vscode from "vscode";
export function activate(context: vscode.ExtensionContext) {
const TERMINAL_NAME = "opencode Terminal";
// Register command to open terminal in split screen and run opencode
let openTerminalDisposable = vscode.commands.registerCommand(
"opencode.openTerminal",
async () => {
// Create a new terminal in split screen
const terminal = vscode.window.createTerminal({
name: TERMINAL_NAME,
location: {
viewColumn: vscode.ViewColumn.Beside,
preserveFocus: false,
},
});
// Show the terminal
terminal.show();
// Send the opencode command to the terminal
terminal.sendText("opencode");
}
);
// Register command to add filepath to terminal
let addFilepathDisposable = vscode.commands.registerCommand(
"opencode.addFilepathToTerminal",
async () => {
const activeEditor = vscode.window.activeTextEditor;
if (!activeEditor) {
vscode.window.showInformationMessage("No active file to get path from");
return;
}
const document = activeEditor.document;
const workspaceFolder = vscode.workspace.getWorkspaceFolder(document.uri);
if (!workspaceFolder) {
vscode.window.showInformationMessage("File is not in a workspace");
return;
}
// Get the relative path from workspace root
const relativePath = vscode.workspace.asRelativePath(document.uri);
let filepathWithAt = `@${relativePath}`;
// Check if there's a selection and add line numbers
const selection = activeEditor.selection;
if (!selection.isEmpty) {
// Convert to 1-based line numbers
const startLine = selection.start.line + 1;
const endLine = selection.end.line + 1;
if (startLine === endLine) {
// Single line selection
filepathWithAt += `#L${startLine}`;
} else {
// Multi-line selection
filepathWithAt += `#L${startLine}-${endLine}`;
}
}
// Get or create terminal
let terminal = vscode.window.activeTerminal;
if (terminal?.name === TERMINAL_NAME) {
terminal.sendText(filepathWithAt);
terminal.show();
}
}
);
context.subscriptions.push(openTerminalDisposable, addFilepathDisposable);
}

View File

@@ -0,0 +1,15 @@
import * as assert from 'assert';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
// import * as myExtension from '../../extension';
suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start all tests.');
test('Sample test', () => {
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
});
});

15
sdks/vscode/tsconfig.json Normal file
View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"module": "Node16",
"target": "ES2022",
"lib": ["ES2022"],
"sourceMap": true,
"rootDir": "src",
"typeRoots": ["./node_modules/@types"],
"strict": true /* enable all strict type-checking options */
/* Additional Checks */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
}
}

View File

@@ -82,7 +82,6 @@ resources:
mcpLocalConfig: McpLocalConfig
mcpRemoteConfig: McpRemoteConfig
modeConfig: ModeConfig
layoutConfig: LayoutConfig
methods:
get: get /config
@@ -95,6 +94,10 @@ resources:
textPartInput: TextPartInput
filePart: FilePart
filePartInput: FilePartInput
filePartSourceText: FilePartSourceText
filePartSource: FilePartSource
fileSource: FileSource
symbolSource: SymbolSource
toolPart: ToolPart
stepStartPart: StepStartPart
stepFinishPart: StepFinishPart